12/17/2021 at 12:08:29 AM

SvelteKit's Killer Feature: Endpoints

SvelteKit is great, but endpoints make it a real contender to dethrone Gatsby and Next.JS


When I decided to rebuilt my website using Svelte, I had some challenges in front of me. One key challenge was how I was going to setup my blog. I had previously used Gatsby and was able to utilize it's GraphQL system to fetch my blog posts, but I wasn't sure if I could do the same with Svelte. Then I found out about SvelteKit and a feature known as endpoints.

Every framework, whether NextJS or Gatsby, have their own way of fetching data from the filesystem. Gatsby uses GraphQL and Next.JS allows you to write NodeJS code to fetch data from the filesystem. SvelteKit does something very similar to what NextJS does, in your routes, you can ether have .svelte files which represent client routes of .js/.ts files that represent server endpoint files. While the SvelteKit docs say to write your endpoints in the format endpointName.json.js, you can write them any way you want and place them anywhere in the routes folder.

You can see how I have my routes folder for this website organized. You have a dedicated folder named api that holds all API routes. This has the added advantage that you are always hitting https://sunny.gg/api/* rather than some arbitrary *.json file.

API Routes are great but you still have to consume them. This is where SvelteKit's load function comes in handy. In the <script> tag of every client route, you can export a load function which comes with an argument fetch. Here fetch has the same API as window.fetch and lets you call your endpoints. All together it looks something like this:

// routes/api/hello-world
export async function get() {
	return {
		status: 200,
		body: JSON.stringify({ message: 'Hello World' }),
	};
}
// routes/index.svelte
<script context="module">
  export async function load({ fetch }) {
    const res = await fetch('/api/hello-world')
    const body = await res.json()

    return {
      props: {
        message: body.message,
      }
     }
  }
</script>

This lets you wire up things like a blog very quickly. Because you can use Node API's in endpoint routes, you can easily query content locally using path, fs and others. Here is what the post endpoint looks like for this site:

// routes/api/post.ts
import fs from 'fs';
import path from 'path';
import fm from 'front-matter';
import type { PostFrontmatter } from '$lib/model/Fontmatter';
import { convertSlugToFilename } from '$lib/helpers/formatSlug';
import { parseMarkdownFile } from '$lib/helpers/parseMarkdownFile';

const NOT_FOUND_RESPONSE = JSON.stringify({ message: 'Post not found' });
const NO_SLUG_PROVIDED = JSON.stringify({ message: 'No slug provided' });

export async function get({ query }: { query: URLSearchParams }) {
	if (!query.has('slug')) {
		return {
			status: 404,
			body: NO_SLUG_PROVIDED,
		};
	}

	const filename = convertSlugToFilename(query.get('slug'));
	const pathToPost = path.resolve(process.cwd(), 'static', 'content', 'posts', filename);
	try {
		const file = fs.readFileSync(pathToPost, 'utf-8');

		const meta = fm < PostFrontmatter > file.attributes;

		if (!meta.published) {
			console.log('This is not a published post');
			return {
				status: 404,
				body: NOT_FOUND_RESPONSE,
			};
		}

		const html = await parseMarkdownFile(file);

		return {
			status: 200,
			body: JSON.stringify({ meta, html }),
		};
	} catch (e) {
		console.log(e);
		return {
			status: 404,
			body: NOT_FOUND_RESPONSE,
		};
	}
}

The get function here takes a query param for the slug. It then takes that slug and looks it up in my posts folder, parses the markdown and frontmatter and then returns the resulting data to the screen. It also runs check to make sure the post is published and will redirect the user to a 404 page if the post has not been published yet.

The last great thing about endpoints is you can debug them with apps like Postman and Paw. When I was building the post endpoint, it would have been very painful to try and debug them in the browser. To get around this limitation I used Paw and was able to make GET requests directly to http://localhost:8080/api/post, this let's you sidestep the UI and build these endpoints just like you would a regular API.

My use case for SvelteKit's endpoints for my blog is pretty simplistic and there is much more you can do with it. While I only used the get function in the endpoint. You can also export post, put, delete functions corresponding to CRUD operations. You can read more about how endpoints work on SvelteKit's website here.

I'm surprised that "endpoints" in SvelteKit aren't getting more attention. It's a truly killer and makes SvelteKit a competitive framework to established players like Gatsby and NextJS.