
No Comments Yet
Be the first to share your thoughts and start the conversation.
Be the first to share your thoughts and start the conversation.
Complete source code available till this point of lesson is available at
By logging in, you'll unlock full access to this and other free tutorials on JSM Pro.
Why? Logging in lets us personalize your learning experience, track your progress, and keep you in the loop with new workshops, coding tips, and platform updates.
You'll also be the first to know about upcoming launches, events, and exclusive discounts.
No spam—just helpful content to level up your skills.
If that sounds fair, go ahead and log in to continue →
Enter your name and email to get instant access
00:00:02 Now that you know how to use route handlers, let's put that useful knowledge to the test.
00:00:06 By getting back to our ultimate Next.js course code base, where we have much more code than we had in that dummy application,
00:00:13 but now we are ready to create real APIs.
00:00:17 Specifically, we'll create API routes for creating users after they go through the authentication process.
00:00:24 But just before we do that, as you know, we are treating this as a production-ready application.
00:00:30 So with that in mind, I'll teach you how to create a global response structure that allows you to standardize a way to handle API or server action responses
00:00:41 across your application for consistency, and simplifying error handling.
00:00:45 Let's head over into types and specifically global.d.ts, where we have all of our global types so far.
00:00:53 In there, I'll create a new type that's going to be called action response.
00:00:58 It's going to be of a type T at the start equal to null.
00:01:02 And we can define the type right here.
00:01:05 First thing we'll add is a type of success, which is of a type boolean.
00:01:09 So we know whether we can expect the result or not.
00:01:12 If it is successful, then we can have an optional field of data.
00:01:18 which is going to be of a type T.
00:01:19 So the data is whatever we pass as a parameter into that action response.
00:01:24 And again, we're doing this to make this system scalable because then we can know exactly how each one of our responses is going to look like.
00:01:33 What is its structure?
00:01:35 It's definitely one of the better best practices that makes your code base clean, improves maintainability, And overall,
00:01:42 just improves your developer experience.
00:01:44 If we don't have the data, we must have an error.
00:01:47 So in that case, an error will be of a type object with a message of a type string and details of the error equal to a record that has a string and an
00:02:00 array of strings.
00:02:01 This is typically how an error looks like.
00:02:04 After the error, we can also have the status, which is going to be number.
00:02:08 And this is just a status code of a specific response.
00:02:11 Now, based on this action response, we can have different types of responses.
00:02:16 So let's define them by saying type is equal to a success response to which we pass the t equal to null, which are the values we pass into it.
00:02:27 it'll be equal to an action response to which we pass the t but we can say and success will be set to true because it is a successful response in the same
00:02:41 way we can create an error response which is going to be equal to the action response to which we pass the undefined as the value,
00:02:49 and the success will be set to false.
00:02:51 In a similar way, we can have something like a type of API error response, which is going to be equal to next response to which we pass the error response.
00:03:03 So check this out.
00:03:04 So remember when we use the nextResponse.json in the crash course on route handlers?
00:03:09 Well, nextResponse is a wrapper around the base HTTP API response.
00:03:16 It's actually modifying the original one and appending additional properties.
00:03:20 So by saying that the API error response is of a type nextResponse within our editor, we'll immediately have more information on different types of properties
00:03:30 that we can use on that response.
00:03:31 And we can have a regular API response by saying type API response.
00:03:37 is equal to and to it we're going to pass the t equal to null if we do pass some values and that's going to be equal to next response success response
00:03:48 to which we pass the t or it could also be of a type error response great i know this can be a bit confusing but hopefully it's going to make more sense
00:03:58 once we actually use them when we create our api routes So let's do that right away.
00:04:04 At this point, you should know exactly how to create our API routes.
00:04:07 So let's do that.
00:04:08 I'll head over to app API and I'll create a new folder called users.
00:04:14 Within users, I'll create a new route.ts.
00:04:20 And within here, we can implement a get request to get all users.
00:04:24 This time we won't be using a fake database.
00:04:26 No, no.
00:04:27 This time we'll connect to a real MongoDB database.
00:04:31 So let's say export async function get, same as before.
00:04:38 In this case, we're going to open up a try and catch block because we want to handle the errors.
00:04:44 In the catch, I will simply return the handle error, which is a utility function we created before, to which I can pass the error.
00:04:52 And I'll say that this is an API error.
00:04:56 And now we can define it as API error response.
00:05:00 So if we do get that error, it's going to contain all the additional metadata.
00:05:05 So once we do return that error, once the handle error function processes it and returns it, and once we get it on the front-end side,
00:05:13 if something goes wrong, we'll be able to know that this is of a type next response error response.
00:05:20 Okay, now let's actually connect to the database by saying await db connect.
00:05:27 This is coming from lib mongoose.
00:05:29 And then we can get the users by saying const users is equal to await user.find.
00:05:37 And this user is, of course, relating to the database user model.
00:05:41 So once we get all the users, so that's why we're not passing anything into the .find, because we want to get all of them,
00:05:47 we can simply return a next response, .json, and we're going to form a special structure.
00:05:55 By first passing the success is equal to true and then passing the data equal to users.
00:06:02 And finally, I'm going to pass another object where the status will be set to 200. So why are we doing this?
00:06:09 Saying success, true data is users and so on.
00:06:13 Because once we try to call this function by saying const getUsers is equal to a wait, and then we can do a call to getUsers or something like that.
00:06:22 Sometimes here you might get users.
00:06:24 Sometimes you might get profile photos.
00:06:27 Sometimes you might get actual questions, but this time you're always going to get just data.
00:06:33 Okay?
00:06:34 So you can destructure the success variable, which is either going to be true or false.
00:06:39 And then you can destructure the data and then you can rename it into whatever it is.
00:06:44 In this case, the data represents the users.
00:06:49 Hopefully that is starting to make a bit more sense, but now let's use our thunder client or postman or whatever else you're using to test this functionality.
00:06:59 And since this is a GET request, you can also test it out within your browser.
00:07:03 That's exactly what we wanted to see.
00:07:05 But of course, up to this point, we have no users, so it's not super exciting.
00:07:09 So now, let's actually create a new user.
00:07:12 But which fields do we actually need to generate the user?
00:07:16 Well, we can know by looking at our user schema.
00:07:19 You can think of this as our backend validation.
00:07:22 Kind of like, hey, I need these fields to actually be able to create this user document.
00:07:28 And then we also have the TypeScript interface for the user.
00:07:33 You can think of this as a validation for our IDE for developers while we're typing out the code.
00:07:39 But hey, what about the validation on the front-end side?
00:07:42 How can we let the users validate their inputs before they try to create a user?
00:07:47 We can head over to the lib folder.
00:07:49 So that's going to be under lib and we can head over to validations.ts where we currently have schemas for ask question,
00:07:58 sign up and sign in.
00:08:00 Within here, we also need to create another export const user schema.
00:08:06 which is going to be equal to z, which stands for zod.object, and we can say which properties do we need to have.
00:08:13 And of course, with it, we can provide additional error handling, like name is going to be equal to z.string with a .min of one character and saying message
00:08:25 is required.
00:08:26 We need to have the username equal to z.string.min3 and we can say something like username must be at least three characters long.
00:08:37 Next, we need to have an email which is going to be z.string of .min1 saying the email is required.
00:08:44 But instead of that, we can actually just use the zod.email property saying please provide a valid email address.
00:08:53 we need to have a bio, which is going to be optional.
00:08:56 So we can say z.string.optional.
00:09:00 And again, how do I know what are these fields that I'm writing?
00:09:03 Well, they're coming right from here.
00:09:05 So if I put this side by side, you can see that we have a name, username, email, bio is optional, and then all of these other fields.
00:09:14 So let me create them.
00:09:16 That's going to be image.
00:09:17 It's going to be z.string.
00:09:19 It has to be a URL.
00:09:21 So if it's not a URL, we can say, please provide a valid URL, but it will be optional.
00:09:28 Next, we have a location, which is going to be a string of optional.
00:09:32 We have a portfolio, which is going to be a string of URL optional.
00:09:38 And we have the reputation.
00:09:40 This reputation will be an optional number.
00:09:44 So we can just say z.number.optional.
00:09:47 Feel free to pause the screen and type it out in case I was typing it out a bit too fast.
00:09:52 But if you're thinking about this user doc schema as the back-end validation, TypeScript as the validation as developers,
00:10:00 think of Zad validation as the front-end validation for the users.
00:10:04 So now, finally, we can try creating a user.
00:10:07 And the reason why I'm saying finally is because we have to do all of these things just to get there.
00:10:12 create some kind of a global action error handling response, create front-end schemas, and then create additional routes,
00:10:20 instead of just simply doing what we need to do.
00:10:22 If you are creating a small, very minimal application, you would be able to just immediately dive into code.
00:10:28 But with production applications, you have to have these additional files because in the long run, they'll make your application that much more scalable,
00:10:37 maintainable, and less error-prone.
00:10:40 So now, let's head over to App, API users route TS.
00:10:45 And let's create another function that allow us to create our users in the database.
00:10:51 We can say export async function post, which accepts the request of a type request.
00:11:00 And we can open up a try and catch block.
00:11:03 I will consider this post action, which is actually going to be a create user action.
00:11:09 as our first real server route handler of our big application.
00:11:14 Because we'll be connecting to the database and trying to perform a real CRUD action.
00:11:19 In the error, let's simply say return handle error to which we pass the error and API as API error response.
00:11:28 And in the try, we can try to connect to the database by saying await dbconnect.
00:11:35 We can then extract the values from the body by saying const body is equal to await request.json.
00:11:42 After that.
00:11:43 Check this out.
00:11:44 We have to validate the data to see whether it's in the right format to be passed to our database.
00:11:52 How cool is that?
00:11:53 And wait, how can we do that in the first place?
00:11:56 Well, you can do it by saying const validated data is equal to user schema.
00:12:04 Okay.
00:12:04 So we're using the schema, which we created in Zod validations.
00:12:13 Then if not validatedData.success, we're going to throw a new validation error to which we can pass the error message of validatedData.error.flatten and
00:12:25 pass the error message.
00:12:28 We're using it right here.
00:12:30 And we're saying, Hey, if you don't pass the right data from the front end, we're not going to pass the right data from the front end.
00:12:35 We're going to pass the right data from the front end.
00:12:38 We're going to pass the right data from the front end.
00:12:40 We're going to pass the right data from the front end.
00:12:42 We're going to pass the right data from the front end.
00:12:45 You are not allowed to make this request and we'll make that known on the front end by throwing a new validation error and passing all the field validations.
00:12:55 So it exactly says, hey, you need to put an email, right?
00:13:01 Or you need to add a more proper password or a more proper name.
00:13:05 Whatever it is, we are returning the right error message to the front end.
00:13:08 After that, we can destructure the things that we need from the data by saying const email and username.
00:13:17 And that's going to be equal to validated data dot data.
00:13:21 Why do we need to get that?
00:13:23 Well, to see if we maybe already have an existing user.
00:13:26 If we have an existing user, we will not try to create a new one.
00:13:30 So we can say const existing user is equal to await user dot find one.
00:13:38 And we're going to try to find it by email.
00:13:42 And by the way, take a look at how easy it is to query documents using Mongoose.
00:13:48 You just use this model, user model we created before, and it automatically exposes for you a find one function to which you can pass the query based off
00:13:58 of which you want to query the documents.
00:14:01 And find one is just one of the functions that we can call.
00:14:05 Soon enough, we'll use the create method as well.
00:14:08 Finally, if there is an existing user, so if existing user exists, then we want to throw a new error.
00:14:18 user already exists.
00:14:20 We cannot proceed.
00:14:21 If a user doesn't exist, maybe a username that a user is trying to create a new user with already does exist.
00:14:29 So we have to make a check for that as well.
00:14:31 const existing username is equal to await user dot find one.
00:14:39 And we want to find it by the username.
00:14:43 So if an existing username exists, we want to throw a new error saying username already exists.
00:14:49 You cannot create a user with that name.
00:14:51 Finally, if we pass all of these checks, which once again, maybe are not required in a super small application, but are absolutely necessary in bigger
00:15:02 production-ready applications that you want to be scalable, we're finally ready to create a user by saying const new user is equal to await user.create.
00:15:13 And to actually create it, we have to pass the data we're sending to this post request.
00:15:19 And of course, that data has to be validated, and we know that it is because we're past this error handler.
00:15:25 So let's say validatedData.data.
00:15:32 that contains the data like the username, email, bio, location, everything else.
00:15:37 So hopefully, the user will have been created by this point, which means that we can return it by saying nextResponse.json,
00:15:46 where success is true, the data is set to the new user, and the HTTP status code is 201, which means created.
00:15:55 Finally, we are ready to test this API route.
00:15:58 by making a post request.
00:16:01 So let's head over to our tester.
00:16:04 I'll change this from get to post and I'll head over to the body.
00:16:09 Now, if you try it with an empty JSON, you're going to get a 500 unexpected and off JSON input.
00:16:16 And I mean, even this on its own is so much better than in our last dummy application.
00:16:22 Remember when we got a 500, when I forgot to add a comma, or used single quoted strings instead of double quoted strings,
00:16:29 we didn't get any kind of error message.
00:16:31 We just got a 500. So even this is so much better.
00:16:34 But now what if we try to create a user with the wrong data?
00:16:38 I'll pass over a name.
00:16:41 is equal to John Doe and click send.
00:16:46 Check this out.
00:16:48 Now we get a 400 with a message saying username is required and an email is required and we get more details.
00:16:57 Hopefully we'll be able to use this on the front end to show some kind of an error message or a warning letting user know that they have to type in those
00:17:04 two additional fields.
00:17:05 Now let's try to create a proper record by passing both the name as well as the username equal to John Doe.
00:17:14 And finally, let's not forget the commas.
00:17:17 Let's also pass an email equal to JohnDoe at gmail.com.
00:17:24 If I try to send a request with this, we get a 201 created with all of the important information, such as the reputation set to zero as it has been newly created,
00:17:35 the created ad, updated ad field, and everything else we need alongside the success that says true.
00:17:41 This means that we have successfully created an API endpoint in Next.js, also known as a route handler, which allows us to create a user.
00:17:51 So I'll head over to my source control and I'll say implement, create, and read user routes.
00:17:59 Commit and sync.
00:18:01 And don't worry, we're going to put that route to use immediately in the next module of the course.
00:18:07 But before we do that, And considering that we're now in this whole backend and API route handlers world, I want to take some time to create additional
00:18:16 API routes, which we'll need to have in our application later on to manage the users.
00:18:20 So let's do that next.