Introduction to SvelteKit Endpoints
Hey, what's going on everyone? My name is Hunter, and welcome to a brand new SvelteKit video. In this video, we're going to be learning about endpoints, or API routes, in SvelteKit, which are defined inside of the +server.js or .ts file.
Now, if we look at the SvelteKit docs here, we can see that the server.js file is going to export some functions that correspond to HTTP verbs like GET, POST, PATCH, PUT, and DELETE. These functions then take in a request event argument and return a response object. So essentially, SvelteKit enables us to create an API within our SvelteKit application that we can consume both inside the application and from other sources outside the application.
Now, we're going to take a look at how we can implement something like this, and we'll be building a little bit of an example project. I'm starting out with a fresh SvelteKit skeleton application and we're going to start by creating a new directory within our routes called api. And then we're going to create another directory inside of the api directory called posts. And then we'll also create a server.js file inside of this posts directory.
Creating a Basic GET Endpoint
And we're going to start off with defining a GET function here, and it kind of seems like we're jumping right into it, but I promise this will all make sense here in just a second. So we can export const get, this is going to be a function, and what this function returns is actually a response object. So we can say return new Response. And since we're building a REST API of sorts, let's go ahead and we need to convert this response body into JSON, and we can do so by running json.stringify and then passing some JavaScript object here. So we can just say message is hello. And then this also takes in an optional init which is kind of where we can set the status and some options. So let's just say status is 200 for example.
And then let's just test this endpoint out. And I'm going to be doing all the API testing through Thunder Client, which is an API client or an HTTP client I should say, similar to Postman, except it's an extension for VS Code that makes it, you know, super easy to test your API within VS Code without having to go back and forth. So let's create a new request. It's going to be to localhost:5173, which is where my app is running right now. And we'll say /api/posts, and then we can click send. And you can see that we in fact get back that message. And then we can modify the status code, for example, and you'll see that we do in fact get that status code modified on our client as well. So this is cool and all, right?
Handling Request Headers for Authorization
But let's actually start to extract some information from this request. So if we look here on the documentation, it takes in a request event argument. And if we click on request event, we can see that we get access to all this information, one of which is request. So let's go ahead and pull request out, and that's going to give us access to headers, right? So let's just say that our app, our API, we want to have authorization headers that need to get passed in order to access the API. And if they're not passed and they're not valid, then we're not going to let them access the API.
We can actually pull out the authorization header by saying const authHeader = request.headers.get() and then we'll just say authorization. Right? Then we can console log the authHeader real quick just to see what it looks like.
Now inside of our HTTP client or Thunder Client, we're going to say headers, we're going to go down here, we're going to type in authorization, we're going to pass my-auth-header as an example here, and then we'll click Send to see it logged to the console. So we do get access to this, right? And then I guess a little bit of an example scenario would say like, if there's no auth headers—and this would obviously be where you would run your your checks to make sure that auth header is actually a valid token of sorts—but for this example, we'll just say if there's no auth header or we'll say if the auth header is not equal to, say, my-auth-header, then we want to return a new response. We'll say json.stringify. The message will be invalid credentials, and then our status code is going to be 401 for unauthorized.
Now let's just test it without or with the authorization header. And then we'll just change this to 123. Oh, it's invalid. And if we just take this off altogether, we can see that it's still invalid, right? So we're not able to access that. So that's kind of how you can set up some authorization into your API endpoint.
Using Environment Variables Securely
Okay, so now let's say that we have an environment variable that contains an API key, and we need that API key to go out and reach out to an external API to pull some data, and we don't want to expose that on our client side. And we have multiple load functions that would require making the same request. We can just define it in this endpoint here, and then from our load functions, we can just fetch this endpoint, and that will return all that information for us.
So let's just pretend that this dummy JSON posts API here requires an API key of sorts. So inside of our file explorer here, we can see that we have a .env file defined and we just have a variable named SECRET_API_KEY. To access this inside of our server.js file is actually super, super easy. We can just say import { SECRET_API_KEY } from '$env/static/private'. Now what we can do is we can just console log the SECRET_API_KEY for example and then let's just test by sending a request here. We can see that we do in fact get that API key inside of our server.js file. So that's how you would use environment variables and API keys inside of these requests as well. And this will never be exposed on the client so that's great.
Fetching External Data in an Endpoint
All right, now let's go ahead and fetch this data from the dummy JSON API and then return it back to the user if they have, you know, an auth header for example. So we can say is we can say const res = await fetch() and then we'll just pass this in. const data = await res.json(). And we actually need to make this an asynchronous function now because we're running some asynchronous code. And then instead of just returning this message hello, we'll just return data.
Now whenever we make a request for posts, you can see here we're going to get back all the same information that we received here, right? Pretty cool. This could also be, you know, you going out and fetching information from a database, for example. You could do that here as well.
Accessing URL Query Parameters
So let's just say that we want to also give users the ability to limit and filter posts, right? So if we look at the dummyjson API, we can see that they have the ability to limit and skip posts by passing some query parameters, and we want to extend that capability to our, you know, private API endpoint. What we can do is we can also take in a url here and then we can actually access those specific query parameters like this. We can say const limit = Number(), we're going to convert it to a number first, and then url.searchParams.get(), we'll say limit. And I'll show you what this is doing in just a second. And then if this does not exist, we want to set the default to let's just say 10. And we can also set one for a skip, and this will be set to zero by default. And we skip, and then this will be skip. Now we can convert this response, or we're going to convert this fetch request into a template string here. And we can pass in ?limit= ${limit} and &skip= ${skip}. Right?
So now let's test this out inside of our Thunder Client. We can say ?limit=10&skip=10. So we're going to skip 10 and we're going to get 10 back. So our first post should be for ID number 11, and we should only get 10 of them. So let's hit send. So you can see now we're getting, we're starting with 11 because we skipped the first 10, and we only have a total of 10, right? So that's how we can access query parameters. And these don't have to be limit or skip, it can be whatever you like. We can just say, for example, we could change this to dog just to give you an example as to what you can do. Obviously, limit and skip are our API standards, but we can just say dog and skip, right? Let's save this, and then we'll change the limit to dog. Uh, let's see here... Oops, I gotta change this as well... dog. Now let's make that request, and you can see that we get the same response, right? So let's obviously change this back to limit here so it's not too confusing. But we can access whatever query parameters you want to provide in your API. You can provide them and then access them like this, right?
Implementing a POST Endpoint
So that is getting data. Let's talk a little bit about posting data to the endpoints, right? So let's say export const post = async and we're also going to take in the request because we're going to need to get the request body. So we'll say request here. And then what we're going to do is we're going to pull the body out by saying const body = await request.json(). That's going to convert the... it's going to parse the JSON out of the response body. And then we'll just console.log(body) just to show you what this is going to look like.
And then we'll create a new request inside of our Thunder Client and we'll just duplicate this one here, and we'll change it into a POST request. And we'll just be posting to /posts. And then the body will say: title: 'My First Post' and the body, or the content, let's say, is going to be My amazing post. Right? Now, when we send this here, you will see that we're getting an error. What is happening here? Oh, handler should return a response object. So I made a mistake here of not returning a response object. Let's return a new response, json.stringify, and we'll just say message: 'success', and we'll say the status code is going to be 201 for created because they just created something, for example.
Now let's make this request again, sorry about that. Now you can see that in the console, we get the information that we passed through this request. So we get access to title and content. So we could just say body.title, body.content, we could access all those variables here as well. And let's just go ahead and put our auth header check here as well. And we'll actually need to get the header from the headers again. And again, POST requests work the same way. So if we don't have this authorization header set, we're going to get a 401 Unauthorized. And if we do have it set, you can see that we do in fact get that posted to the server. Cool.
Consuming the API from a SvelteKit Page
So let's actually start implementing this with an application that we're developing for a blog, for example. So let's go back into our File Explorer here. We're going to close up the API for now, and then we're going to create a new directory inside of the routes we'll call it posts, it'll be a blog, right? And then inside of this, we're going to create a +page.js and then we'll also create a +page.svelte.
Now for the +page.js, if you're not familiar with loading data in SvelteKit, I have a video on that. I'll leave a card in the top right as well as a link in the video description. But we're going to export a load function and this is going to return the posts. So this load function is actually going to take care of going out and fetching the posts from our own API that we built and then returning that back to the page.svelte file so we can display them, right?
So let's go ahead and set up some initial structure here within our page.svelte. So we'll set up a script tag. We know that we're going to be receiving data from the page.js file. And let's just set up an h1 with the title and a p-tag with the body.
Right now inside of our page.js, what we can do is we can actually say const fetchPosts, we'll define our method, a function here, equals async. And we actually need to take in fetch from the load function because this is going to be an internal request here. We'll say const res = await fetch. Now this is what's really cool about using this fetch here we pass it into page.js, is that we can use a relative request. So we can just say for example /api/posts, right? Which is /api/posts, it's going to hit this server, right? It says we're making a GET request by default with fetch, it's going to hit this get function here, right?
Now we can say const data = await res.json(). And then since this is going to give us back an object with posts and then an array of posts, what we want to do is we actually pull out this posts here. So we'll return data.posts. Now what we can do is we can actually return this function by saying return { posts: fetchPosts() }. Right? Now we'll have access to those posts inside of this data prop here. So we can set up an each block. So we say {#each data.posts as post}. We'll move up title and body and then we'll say post.title, whoops, post.title and post.body. I think that's what they're called, right? Yep, title and body over here. We're getting the title and body from this API. So we're going to have this array, we're basically going to have an array of posts inside of data.posts, right? And then for each post, we're going to place the title in an h1 tag and the body in a p tag for our page.
Right now, let's click Save, and then we'll make sure this is saved as well. And let's navigate to that URL, so we can go to /posts. And we're getting an undefined property here. Can I read properties of undefined reading length? data.posts... that should be an array... {#each data.posts}. So this gives us back... this fetch method here... this fetch call here gives us back... Okay, so we didn't actually, we actually don't have an auth header set on our fetch request. So let's just take this out for the time being. So we'll just remove the auth header check here on this one. And now we can see that we do in fact get all these posts displaying here, right? We have that default limit of 10 set still. So let's go ahead and save. Now we get access to all of these posts. Cool.
Creating Dynamic API Routes with Parameters
So let's just say that now we want to be able to click on one of these posts and bring us to the full post page, right? We can also implement that within our API as well using the params. So inside of API and posts, we can create a new directory called [postID], and then we can say a +server.js will go in there as well. So now we have a param. So now we can say export const get = async and what we're actually going to pass in here again is going to be the params, right? So now we're going to be able to access the params the same way we would do that with a normal route in SvelteKit. So we can console log the params really quick and just show you what that's going to look like. And we'll return a new response.
Go back into our Thunder Client here. We will say, let's see here... We'll make a request, a GET request to this URL now. Remember, slash whatever comes after post is going to be that postID param. So we could say, oh say we want to get post 25 for example. Now if we remove this, we can remove this JSON content here because we're not actually passing anything, we're not making a POST request. When you remove this body and then hit send, you're going to see that we get message: success, right? So now what we can do is we can actually go out and get this specific post information and return it on this page as well. So we can just say const res = await fetch. And we could see obviously down at the bottom that we're getting the post ID here in the console. So we can say fetch, and then we'll say, we'll use some template strings, dummyjson.com/post/ and then we'll pass in the params.postID. And then data, and then we'll just pass data here.
Now when we make a call to post 25 for example, we're going to see that we're going to get the post with the ID 25, right? Which means we can also do the same thing inside of our posts directory outside of the API route. So we can just say for example [postID] and then we'll make a +page.js and we'll also make a +page.svelte. And then inside of the page.js we'll say export const load = this will pass in fetch, we'll say const fetchPost. This is actually going to take in a parameter of an ID. We could say const res = await fetch('/api/posts/' + id). Right, because this is going to be... we're actually hitting our own API with this one. Then data is await res.json(), and we'll return data.
And then inside of our load function, we can just return { post: fetchPost() }, passing in the params or the params, which we need to actually import. So say params, params.postID. And this may be a little bit confusing because we're using the same syntax here, but this is entirely... this is an entirely separate param than this param. So this param is internal to our SvelteKit application, this param can be used by anything, right? So let's return that. And then inside of our, let me close out some of these first. Instead of our page.svelte for the individual post page, we can just export let data to accept that data prop. And then we'll set up another div here and we'll say h1 is going to be data.post.title, and then the p tag is going to be data.post.body.
So now if we navigate to, for example, /posts/25 in our application, we now get the 25th post here, which is happening by our load function hitting our own API, which then goes out and hits the dummy JSON API and returns that data, right?
Conclusion & Final Thoughts
So that's essentially how you can use endpoints. Again, I'm not sure if this example was a great one, but I wanted to show how not only you can, you know, set this API up to be consumed by multiple different endpoints or multiple different clients, but you can also consume it from your own application. Again, really in this, in this exact scenario that I outlined here, you'd probably want to just use the load function, the page.server.js load function to handle this because we're only accessing it really in one place. But just to show you an example of how you could access that same API endpoint from anywhere in your application or outside of your application, right?
So I hope this video has been informative for you all. If you found it useful, I would appreciate if you would like and subscribe. If you guys have any more specifics you'd like me to cover with regard to endpoints or anything related to SvelteKit, I'd be more than happy to take into consideration. We also just recently started a Discord server, so feel free to join. We're trying to have some, you know, meaningful conversations in there about SvelteKit and some of the best practices. So feel free to hop in there, ask me any questions you may have, and if not, I will see you guys in the next video.