Next.js Fundamentals, v4

Creating API Routes

Scott Moss
Superfilter AI
Next.js Fundamentals, v4

Lesson Description

The "Creating API Routes" Lesson is part of the full, Next.js Fundamentals, v4 course featured in this preview video. Here's what you'd learn in this lesson:

Scott demonstrates how to create routes for a developer API, specifically for getting all issues and creating a new issue. He also shows how to create a get request to retrieve a specific issue by its ID.

Preview
Close

Transcript from the "Creating API Routes" Lesson

[00:00:00]
>> Scott Moss: All right, so let's check out making our own routes. What we're gonna do is we're gonna make a route for a developer API. We want people to develop and, or actually, before I do that, I think someone asked me how to develop an API that was OpenAPI spec compatible.

[00:00:21]
I think somebody asked me that. I added some notes on that. And the short answer is there is no official way to do it in Next.js. There's been tons of people in the community have made it possible. After researching and trying a bunch of stuff, I found the best way to do it was actually to use this package right here called Next SwaggerDoc and Swagger UI React.

[00:00:45]
It essentially looks at, I guess it's very similar to how you would do it in Python with a doc string. It looks at your comments and it can go from that and create the spec, and also create the UI for it as well. So, I left the link here if anybody wants to check that out.

[00:01:04]
But this is the best that I could find to do this. There was no official way to do it. Other than that, let's make our route. So we're gonna create some routes for our developer API. We want people to be able to get all the issues, create an issue, and then get one issue.

[00:01:20]
So we're gonna make routes for that. So I'm gonna go here, I'm gonna delete this user one, get out of here, and make a new folder for issue. And sibling to that, I'll make a new folder. Wait, no, that's the only one I need. So issue and then inside of issue I'll make a new folder for id, since we wanna get one issue.

[00:01:46]
And then inside of that I'll make a route.ts. Right above that I'll make a new file called route.ts. So for the ones without the ID, if you just follow rest, this would allow me to get all the issues and then create one issue. If I followed rest and the ID one would let me delete one issue, update one issue, get one issue.

[00:02:18]
So let's do this. So let's get all issues. So I'll say, export const GET And then we have the one for post. We'll do that in a minute. So we wanna get all the issues, should be pretty simple. We're not gonna make it production-ready and do all the things that we need to do that you would typically do for Production app.

[00:02:55]
X request, there we go. So it should be pretty simple, because again, we're on the back end, so we can do the same stuff that we've been doing on the server actions. And this one's pretty simple, we just wanna just grab all the issues and send them back.

[00:03:09]
So, let's do that. And then, in the case where we fail, we can change the status code to something else. So let's do that. So I'll say, issues equals await db.query.issues.findmany, let's just grab every issue in the database. We'll do that, and then we'll return NextResponse.json. I always like to return an object with a data property on it so it's consistent, and just return issues.

[00:03:49]
Otherwise, let's log this. And we'll return NextResponse., you can return JSON here if you want. If you wanna send back like a message, like send back error if there's an error. And I'll say, nah. And then, you can change the status, or actually, I think it's here now.

[00:04:12]
Yeah, you change the status to whatever you want in this case. You couldn't get this, it errored. That's on us. There's no reason why you couldn't get this. So if it broke, that's our problem. So, 500. Cool, we got that. Let's test this out. So we're gonna go here, I say, issues, change this to get, and send this request.

[00:04:41]
It's issue. Method, I swear I changed that to git. There we go. There we go, there's all my issues. Cool, do the same thing for creating an issue. So for this one we do wanna get the body. I'll say, newIssue = awaits db.insert(issues).values. I want to insert await.

[00:05:31]
I gotta import issues up here. What do you mean? No, that comes from schemas. Yeah, schemas. Okay, so then I got to await rec.json. That's gonna be the value. Whatever object they pass up, that's what we'll put in here to insert to the database. And, actually let's try catch this.

[00:06:05]
Cool, got a new issue. And then I'll just return that NextResponse Data, and for this one, Drizzle's always gonna return an array here. So I just wanna return, I'll just say new. I'll just do this. And it's freaking out, it's like, hey, that might not be there. No, I gotta do .return here, actually.

[00:06:47]
There we go. There it is. And then if it's an error, same thing, console.error this. This case, it actually might not be a firewriter, cuz they might have set up the wrong thing. This is where you wanna validate. This is why I was like, hey, I'm gonna validate the form on the server action, the inputs.

[00:07:11]
Because if there's an API route, I'd validate that here. I'll do the same thing. I set up schemas and validate stuff before I touch the database. Okay, let's try to create an issue. Let me just look at the schema right quick to see what an issue needs before I do it.

[00:07:29]
So issue needs a title, and that's it. Everything else is either not needed or already has a default. So it just needs a title. So we can just go make an issue with the title. So let's do that. So I'll change this to post. I'll go to my body, put a title here.

[00:07:53]
Normal mode. And I'll send this up. We got a nah, let's see what the error was. Felling roll contains [INAUDIBLE]. Need a user id. Let's get the current user. Yeah, we can just use, this is where the flexibility comes in. I can just say, user = await get cetCurrentUser.

[00:08:32]
I can just use that function. It works on the server. And then issues, need a map over these. Not these, this right here. This needs a, Let's get this, newIssueData equals that. This is userId, I'm guessing, that's what it's gonna be called. And then, newIssueData. What we wanna do now is we were making the post request to create an issue.

[00:09:13]
We need to make sure that the issue has a user ID on it. Because I'm using a HTTP client that doesn't have a session cookie, I'd have to add the JSON web token here in the cookie, which I don't want to. Instead, I'm just gonna have this thing sent up, the user ID.

[00:09:35]
So what I can do is, I got my Drizzle Studio working. I had it running on another tab over here, so I had to stop that. Now I'm here. And now it works, so I can go here to Drizzle Studio, it'll show me my database. I'm gonna grab this user's ID right here, gonna go back into my project.

[00:09:58]
Gonna make a post request to localhost3000/API/issue. I'm going to add a body of JSON, give it a title of new issue here. Give it a user ID of that user ID, and hit Send. And there we go, created a new issue for this user. And then the last one we wanna do is get one issue.

[00:10:35]
So I'm gonna go into my/API/issue/id route.ts. I'm going to export a GET request to get one issue here. I'm going to get the request, and then I'm gonna get the params. I'll go ahead and type check these. I know I got got those folks that really like it.

[00:11:01]
So, Next Request. And then for params, or actually, it'll be like Something like that. Okay, so, I believe we still have to await this. So I'll do, wait params, get the id, and then issue will be wait db.query.issues.findFirst where, Issues.id is the id, but I think I need to parse int that id maybe.

[00:11:58]
And I'm just gonna try catch this. Cool, and if we don't find it, Where does return. JSON here, I'll just put an error here. And status, I'll just put 404, whatever. If we do find it, I'll just return it. Cool. So we can try this. Let's grab an issue.

[00:12:52]
So if I go here, I grab, this issue is 11. So I can go here, I'll make a, we can go back to this one. Get request to/API/issues/that ID. I don't need anything in a body to GET requests, so it shouldn't send it anyway. There we go, get the issue back.

[00:13:15]
It's pretty simple. Because it's using standard web stuff when it comes to request and response, everything's pretty much compatible with it. So if you wanna add a GraphQL server, every GraphQL server from Apollo to GraphQL Yoga to, pick your flavor, works with this because they all support the specs.

[00:13:36]
So you can pretty much do anything. There's frameworks that people have built on top of this to make it feel and look more like Express.js, if that's something you want. But, yeah, I mean, TRPCS is a good use case. People use this for, it's like, it's so easy to use this, in my opinion.

[00:13:52]
And really, I didn't import anything from Next.js other than these two things, which are really just like wrappers around this. I don't have to use these, I could use just the native response if I wanted. It's just a lot more work to Get.json. There's just a wrapper around that.

[00:14:08]
Yeah, there really isn't that much more to it. Like I said, there are some special things. But the reason I didn't show that is because it depends on whether or not you deploy to Vercel or not. So I was like, eh, I don't wanna show stuff that's only specific to Vercel and doesn't work if you deploy this somewhere else.

[00:14:23]
But there are some other things that you can do. Like I mentioned, being able to, a very common use case is if you ever had to make a handler for a webhook is, when a service hits your route as a webhook, like, let's say Stripe hits your API.

[00:14:38]
You want to immediately respond back as quickly as possible to acknowledge that you got the request. But there's probably some work you need to do when that comes in. So, Stripe hits your API and it's like, hey, this person's charge failed. Okay, there's some work that you need to do.

[00:14:55]
You don't want Stripe waiting on you to do that work, cuz what if that work fails? Do you send Stripe back an error? Well, if Stripe got that error, to Stripe, that means you didn't get the webhook. So they're gonna send it again. That's not actually the case.

[00:15:08]
So when you receive a webhook, you want to respond back with a 200 as quickly as possible to acknowledge that you got it, and then you wanna do your work. Well, that's impossible in serverless, because as soon as you respond, you stop executing code. You can't do anything after this.

[00:15:23]
So if I respond back to Stripe, how do I do something? So typically what you would do is you would use a background, you have another job queue, a queuing system that's event driven, and then you queue the job up and you go do something. That's a way.

[00:15:37]
The other way you can do it is Vercel or Next.js through hosting on Vercel allows you to respond immediately and then wait until all your work is done before the service function shuts down. So even though you responded immediately, the function continues to run the code that you want it to wait for even though you already ended the request.

[00:15:58]
So that's a really good use case. And then as far as databases and stuff like that, It's the same. Well, I guess it is, I do want to mention, when it comes to serverless, that for a serverless environment, you have to have a serverless database. You can't use a database that isn't HTTP based.

[00:16:23]
I mean, you can, but you'll have problems. Because most databases have long lived connections with pooling. But the way serverless works is that it's spinning up a vm, running your function and then shutting it down at its most naive implementation. So you aren't on a traditional long-lived server that's pooling connections across requests.

[00:16:43]
So if that database even works, you will for sure run into pooling connection issues. Most databases have pooling limits in which they can only connect so many connections before they don't do it anymore. You'll have memory issues, you'll have cold start issues. So typically, you wanna use an HTTP-based database, AKA a serverless database, for your serverless API.

[00:17:05]
Neon is a serverless database, so it kinda works perfectly for Next.js. And almost any modern database today has some serverless offering. If you want Postgres, there's five, six serverless Postgres databases out there. You want SQL, same thing. There's tons of them. So, just remember that you want to make sure you have a serverless database, or I should say, HTTP-based database.

[00:17:28]
Okay, this-
>> Speaker 2: I think of one question on API stuff. I'm assuming all the same conventions for the router is the same of, you can create route groups and spread dynamic params and everything in your directories.
>> Scott Moss: I've never created route groups with the API, but you can definitely spread.

[00:17:46]
Yeah, route groups.
>> Speaker 2: I don't know why you would.
>> Scott Moss: Yeah, I don't know why you would, because you don't have layouts, so there's no point. But the spread definitely works. Yeah, or they used to. I haven't tried it yet, so [LAUGH] don't quote me on definitely. It used to, I haven't tried it, so I cannot say that it still does.

[00:18:02]
But yeah, I don't think the route groups do.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Start a 7-Day Free Trial