Skip to content

Server-only load functions #1922

Closed
Closed
@hdoro

Description

@hdoro

Describe the problem

Thanks for building kit and all the heroic efforts in dealing with this amount of issues!

As the load function runs in both client & server, we always find ourselves creating an extra endpoint for fetching data with server credentials. This is laborious and tedious, leads to duplication of error handling and makes it harder to reason about data flow.

In the example below, notice how /recipes/[slug].svelte and /api/recipes/[slug].js have a lot of duplication:

<!-- /recipes/[slug].svelte -->
<script context="module">
  export const load = async ({ page: { params }, fetch }) => {
    const { slug } = params

    if (!slug || typeof slug !== 'string') {
      return {
        status: 400,
        error: 'Recipe not found',
      }
    }

    // No way to click to open endpoint definition
    const recipe = await (await fetch(`/api/recipes/${slug}`)).json()

    if (!recipe?._id) {
      return {
        status: 404,
        error: 'Recipe not found',
      }
    }

    return {
      props: {
        recipe,
      },
    }
  }
</script>
// /api/recipes/[slug].js
import getFullRecipe from '$lib/queries/getFullRecipe'

export const get = async (request) => {
  const { slug } = request.params

  // Duplicated error handling
  if (!slug || typeof slug !== 'string') {
    return {
      status: 400,
      error: 'Recipe not found',
    }
  }

  const recipe = await getFullRecipe(slug)

  if (!recipe?._id) {
    return {
      status: 404,
      error: 'Recipe not found',
    }
  }

  return {
    status: 200,
    body: recipe,
  }
}

Describe the proposed solution

Ideally, Kit's adapters would automatically transform server-only functions into their own endpoints, without us having to think about it. This would be similar to Next's getServerSideProps.

To annotate a function as server only, we could pass a context="server" tag to the module <script> tag. Here's the example above with this approach:

<script context="module" context="server">
  import getFullRecipe from '$lib/queries/getFullRecipe'

  export const load = async ({ page: { params }, fetch }) => {
    const { slug } = params

    if (!slug || typeof slug !== 'string') {
      return {
        status: 400,
        error: 'Receita não encontrada',
      }
    }

    const recipe = await getFullRecipe(slug)

    if (!recipe?._id) {
      return {
        status: 404,
        error: 'Receita não encontrada',
      }
    }

    return {
      props: {
        recipe,
      },
    }
  }
</script>

Single file, ~50% code reduction (29 LOC vs. 55 in the example above), one less file to manage ✨

Alternatives considered

I'm considering creating my own abstractions to make this slightly easier to reason about. For example, I could condense all data-fetching server endpoints into a single get function that takes whatever query I need to perform and returns the data accordingly.

The issue here is that it still wouldn't be as simple as a native solution and we'd be opening up a lot of dissonance between different codebases.

I understand this is potentially a very hard issue to tackle, but if doable it feels like a pre-v1 feature to make dynamic routes easier to build & onboard new people.

Importance

would make my life easier

Additional Information

Sorry if I double posted this - searched hard through issues and couldn't find another. So many issues coming in, thanks for putting in the work to deal with all this pressure 🙏

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions