From e378581d1acb9966a53ed0879fa9668d178cf921 Mon Sep 17 00:00:00 2001 From: nevz Date: Tue, 20 Aug 2024 06:12:49 +0800 Subject: [PATCH] NodeJS Controllers: Update lesson to flow with the previous lesson --- nodeJS/express/controllers.md | 288 ++++++++++++++++++++-------------- 1 file changed, 173 insertions(+), 115 deletions(-) diff --git a/nodeJS/express/controllers.md b/nodeJS/express/controllers.md index 522713e2fff..8ac6e160a1a 100644 --- a/nodeJS/express/controllers.md +++ b/nodeJS/express/controllers.md @@ -14,16 +14,16 @@ This section contains a general overview of topics that you will learn in this l ### Handling responses -When it comes to sending responses from your controllers, you have several methods at your disposal. Let's explore some of the commonly used methods and their use cases. +When it comes to sending responses from our controllers, we have several methods at our disposal. Let's explore some of the commonly used methods and their use cases. -- [res.send](https://expressjs.com/en/api.html#res.send) - A general-purpose method for sending a response, it is flexible with what data you can send since it will automatically set the `Content-Type` header based on what data you pass it. For example, if you pass in an object, it will stringify it as JSON and set the `Content-Type` header to `application/json`. +- [res.send](https://expressjs.com/en/api.html#res.send) - A general-purpose method for sending a response, it is flexible with what data we can send since it will automatically set the `Content-Type` header based on what data you pass it. For example, if we pass in an object, it will stringify it as JSON and set the `Content-Type` header to `application/json`. - [res.json](https://expressjs.com/en/api.html#res.json) - This is a more explicit way to respond to a request with JSON. This always sets the `Content-Type` header to `application/json` and sends the data as JSON. -- [res.redirect](https://expressjs.com/en/api.html#res.redirect) - When you want to redirect the client to a different URL, this method allows for that capability. -- [res.render](https://expressjs.com/en/api.html#res.render) - If you're using a template engine (covered in a later lesson), `res.render` allows you to render a view template and send the rendered HTML as the response. +- [res.redirect](https://expressjs.com/en/api.html#res.redirect) - When we want to redirect the client to a different URL, this method allows for that capability. +- [res.render](https://expressjs.com/en/api.html#res.render) - `res.render` lets you render a view template and send the resulting HTML as the response. We'll cover this in a later lesson. -There is also a useful method that you can use to set the status code manually. +There is also a useful method that we can use to set the status code manually. -- [res.status](https://expressjs.com/en/api.html#res.status) - This sets the response's status code **but does not end the request-response cycle by itself**. You can chain other methods through this (e.g. `res.status(404).send(...)` but note that you can't do `res.send(...).status(404)`). You can omit this if you wish to use the default status code of `200`. +- [res.status](https://expressjs.com/en/api.html#res.status) - This sets the response's status code **but does not end the request-response cycle by itself**. We can chain other methods through this (e.g. `res.status(404).send(...)` but note that we can't do `res.send(...).status(404)`). We can omit this if we wish to use the default status code of `200`.
@@ -31,11 +31,11 @@ There is also a useful method that you can use to set the status code manually. If `res.send` automatically sets the `Content-Type` based on the data passed, why would we still use `res.json`? `res.json` enforces JSON and will automatically convert non-object values to JSON, but `res.send` will not. `res.json` is just a convenient method that also internally calls `res.send`. `res.send` will only handle things as JSON when dealing with booleans and objects (which includes arrays). -So for convenience it's more appropriate to use `res.json` instead of `res.send`, and if you're sending JSON, you might as well use a method that's literally named "json". It's like the perfect match! +So for convenience it's more appropriate to use `res.json` instead of `res.send`, and if we're sending JSON, we might as well use a method that's literally named "json". It's like the perfect match!
-We also need to take note that these response methods only end the request-response cycle. They do not end the function execution. For example if you somehow do this: +We also need to take note that these response methods only end the request-response cycle. They do not end the function execution. For example if we do this: ```javascript app.use((req, res) => { @@ -45,51 +45,51 @@ app.use((req, res) => { // However, it does not exit the function so this will still run console.log('will still run!!'); - // This will then throw an error that you cannot send again after sending to the client already + // This will then throw an error that we cannot send again after sending to the client already res.send("Bye"); }); ``` ### Middleware -Middleware functions are a core concept in Express and play a crucial role in handling requests and responses. They sit between the incoming request and the final intended route handler. +Middleware functions are a core concept in Express and play a crucial role in handling requests and responses. They operate between the incoming request and the final intended route handler. A middleware function typically takes three arguments (however, there is one that we will get into later that has four): -- `req` - The request object, which represents the incoming HTTP request. -- `res` - The response object, which represents the HTTP response that will be sent back to the client. -- `next` - The function that pass the control to the next middleware function in the chain (we'll get to this later). This is optional. +- `req` - The request object, representing the incoming HTTP request. +- `res` - The response object, representing the HTTP response that will be sent back to the client. +- `next` - The function that passes control to the next middleware function in the chain (we'll get to this later). This is optional.
#### Naming convention -Names are just convention, you can name them whatever you want `req` -> `request`, `res` -> `response`, etc +Names are just convention, we can name them whatever we want, like `req` -> `request`, `res` -> `response`, etc.
A middleware function can perform various tasks, such as: - Modifying the request or response objects (some packages for example will do this, like adding a new property in the request object, or setting the `res.locals` that is used in templates rendered with `res.render`). -- Executing additional code (validation middleware functions to validate the request before going to your main logic, authentication middleware functions, and so on). +- Executing additional code (validation middleware functions to validate the request before going to our main logic, authentication middleware functions, and so on). - Calling the next middleware function in the chain. - Ending the request-response cycle (meaning no further middleware functions are called, even if there are more in the chain). -Express has a rich ecosystem and you will likely find a package that solves the problem you are encountering. For example, some packages provide middleware functions to handle authentication, cors, rate limiting, sessions, logging, validation, and more! Throughout this lesson (and the course), we'll be introducing middlewares that would be required to build projects in this course. Although, you're welcome to explore on your own. +Express has a rich ecosystem, so you'll likely find a package that solves the problem you're encountering. For example, some packages provide middleware functions to handle authentication, CORS, rate limiting, sessions, logging, validation, and more! Throughout this lesson (and the course), we'll introduce middlewares needed to build projects in this course, but you’re welcome to explore on your own. #### Application-level middleware -Application-level middleware are bound to an *instance of Express* using `app.use` or using `app.METHOD` (e.g. `app.get`, `app.post`) functions. These are middleware functions that are executed in every incoming request matching the specified path. If you don't specify a path, the path defaults to `/` which will match every incoming request. As with any middleware functions, they will not run if the request-response cycle ends before reaching them. Typically, these middleware functions are placed on top of your application code to ensure they always run first. +Application-level middleware are bound to an *instance of Express* using `app.use` or using `app.METHOD` (e.g. `app.get`, `app.post`) functions. Express executes these middleware functions for every incoming request that matches the specified path. If we don't specify a path, Express defaults the path to `/`, matching every incoming request. As with any middleware functions, they will not run if the request-response cycle ends before reaching them. Typically, these middleware functions are placed on top of our application code to ensure they always run first. -Very common built-in middleware functions that you will likely use are the following: +Express offers several essential built-in middleware functions that we'll frequently use in our applications. These include: -- Body parsers (e.g. `express.json`, `express.urlencoded`) - These allow us to correctly parse the incoming request's body, so that you can use it through `req.body`. -- Serving static files (e.g. `app.use(express.static('public'))`) - It is a middleware function for serving static files, such as HTML, CSS, JavaScript, and images. You can pass an argument to specify which directory to serve the static files. -- Setting up views (you will learn how in the Views lesson). +- Body parsers (e.g. `express.json`, `express.urlencoded`) - These allow us to correctly parse the incoming request's body, so that we can use it through `req.body`. +- Serving static files (e.g. `app.use(express.static('public'))`) - This middleware function serves static files like HTML, CSS, JavaScript, and images. We can pass an argument to specify which directory to serve the static files. +- Setting up views (we will learn how in the Views lesson). #### Router-level middleware -Router-level middleware works similarly to application-level middlewares but is bound to an *instance of Express router* using `router.use` or `router.METHOD` (e.g. router.get) functions. This however is only executed when the request matches the specific route as you've probably already learned in the Routes lesson. +Router-level middleware works similarly to an application-level middleware, but it's bound to an *instance of Express router* using `router.use` or `router.METHOD` (e.g. router.get) functions. However, Express only executes this when the request matches the specific route, as you've probably already learned in the Routes lesson. Here is an example of a basic middleware function: @@ -108,75 +108,99 @@ function myMiddleware(req, res, next) { app.use(myMiddleware); ``` -In this example, the middleware function logs a message, adds a custom property to the request object, and then calls the `next()` function to pass control to the next middleware function or route handler. We also register the middleware function through the usage of `app.use` which makes this an application-level middleware. Middleware functions following `myMiddleware` in this chain can now access `req.customProperty` with the value `"Hello from myMiddleware"`. +In this example, the middleware function logs a message, adds a custom property to the request object, and then calls the `next()` function to pass control to the next middleware function or route handler. We also register the middleware function through the usage of `app.use` which makes this an application-level middleware. Middleware functions following `myMiddleware` in this chain can now access `req.customProperty`, which contains the value `"Hello from myMiddleware"`. -One thing to note is that middleware functions are executed in the order they are defined or registered in your application. This means that the sequence in which you define your middleware functions matters, as it determines the order in which they will be invoked during the request-response cycle. So you need to make sure and be aware that your middleware functions are placed in the correct order. As an example, some packages have middleware functions that changes the `Request` object, and as a result, these middleware functions should be placed at the very top of your application in order for you to be able to see their changes in all of your middleware functions below it. +One thing to note is that Express executes middleware functions in the order we define or register them in our application. This means that the sequence in which we define our middleware functions matters, as it determines the order in which they will be invoked during the request-response cycle. So we need to make sure and be aware that our middleware functions are placed in the correct order. As an example, some packages have middleware functions that changes the `Request` object, and as a result, these middleware functions should be placed at the very top of our application in order for us to be able to see their changes in all of your middleware functions below it. There is also a special type of middleware function that handles errors, which we will discuss shortly. ### Controllers -As said earlier, controllers are just functions. They also classify as a middleware (at least in the Express world) that are used by route handlers. -A controller comes into play whenever a request hits the server and a route matches the requested HTTP verb and path. The route determines which controller should handle the request based on the defined middleware chain. The appropriate controller then takes over and performs the necessary actions to fulfill the request. This could involve retrieving data from the model, processing the data, making decisions based on business logic, or updating the model with new data. +As said earlier, controllers are just functions. They also classify as a middleware (at least in the Express world) that are used by route handlers. However, it's important to note that controller and middleware are distinct concepts. Controllers are a key component of the MVC (Model-View-Controller) pattern, a design approach for organizing software. Middleware, on the other hand, is a core feature of Express that allows you to run code, modify requests, or end the cycle at specific points in the request-response cycle. So we are basically using a core functionality of Express to write out patterns of code. +A controller comes into play whenever a request hits the server and a route matches the requested HTTP verb and path. The route determines which controller should handle the request based on the defined middleware chain. The appropriate controller then takes over, performing the necessary actions to fulfill the request.. This could involve retrieving data from the model, processing the data, making decisions based on business logic, or updating the model with new data. -Once the controller has completed its tasks, it passes the processed data to the view, which renders the data into a format suitable for sending back to the client. Typically, this would be HTML. Later, when we cover building APIs, we can also send JSON responses like with the APIs that you've previously encountered e.g. Giphy API. +Once the controller completes its tasks, it passes the processed data to the view. The view then renders this data into a format suitable for sending back to the client. Typically, this would be HTML. Later, when we cover building APIs, we can also send JSON responses like with the APIs that we've previously used e.g. Giphy API. The naming conventions for these controllers are usually based on the route they will be attached to e.g. `GET` route -> `getSomething`, `POST` route -> `createSomething`, `DELETE` route -> `deleteSomething`, etc. Nonetheless, there is no fixed rule since Express is not opinionated. It will always be based on you or someone else's conventions, and the requirements of the function. -Let's define our first controller! Create a folder called `controllers` at the root of your Express project. Then create a `userController.js` file within it: +You should already have your application setup in-place from the previous lesson. Previously, we've defined a route for `/authors/:authorId` to get the author by their ID, let's now define a controller for that route. But to make the example produce something, let's create a mock database with a function that we can use to retrieve an author by their ID. + +Create the following file at the root of the Express project, and add the following code: + +```javascript +// db.js + +const authors = [ + { id: 1, name: "Bryan" }, + { id: 2, name: "Christian" }, + { id: 3, name: "Jason" }, +]; + +const getAuthorById = async (authorId) => { + return authors.find(author => author.id === authorId); +}; + +module.exports = { getAuthorById }; +``` + +The file name, content, and location aren't important here, they only serve to illustrate that we're using a database query function to retrieve an author by their ID. + +Then for the controller, create the following file: ```javascript -// user controller file - controllers/userController.js +// controllers/authorController.js -const getUserById = async (req, res) => { - const userId = req.params.id; +const { getAuthorById } = require("../db"); - const user = await someDBQueryToGetUser(userId); +const getAuthorById = async (req, res) => { + const { authorId } = req.params; - if (!user) { - res.status(404).send("User not found"); + const author = await getAuthorById(authorId); + + if (!author) { + res.status(404).send("Author not found"); return; } - res.send(`User found: ${user.name}`); + res.send(author); }; + +module.exports = { getAuthorById }; ``` -In this example, the `getUserById` function is a controller that handles a specific action related to retrieving a user by their ID. You'll use this controller by importing it into file where routes are defined, and using it like so `router.get("/user/:id", getUserById)`. Let's break down what's happening in this controller: +In this example, the `getAuthorById` function is a controller that handles a specific action related to retrieving an author by their ID. We'll use this controller by importing it into the file where we define our routes, and using it like this: `router.get("/authors/:id", getAuthorById)`. Let's break down what's happening in this controller: -1. The controller extracts the userId from the request parameters (`req.params.id`). This assumes that the parameter is defined with a route, such as `/users/:id`. -1. It then invokes a database query function `someDBQueryToGetUser` to retrieve the user data based on the userId. -1. If the user is not found (`!user`), the controller sends a response with a 404 status code and the message `User not found`, using `res.status(404).send(...)`. It then returns from the controller function in order to not invoke any other logic in the controller, because sending a response does not stop the function execution itself. -1. If the user is found, the controller sends a response with a 200 status code and the message `User found: ${user.name}`, using `res.send(...)`. This assumes that the user object has a name property. We don't need to use the `status` method here since the response sends a 200 by default. +1. The controller extracts the `authorId` from the request parameters (`req.params.id`). This assumes we've defined the parameter with a route, such as `/authors/:authorId`. +1. It then invokes a database query function `getAuthorById` to retrieve the author data based on the `authorId`. +1. If the controller doesn't find the author, it sends a response with a 404 status code and the message Author not found, using `res.status(404).send(...)`. It then returns from the controller function to avoid invoking any other logic in the controller, as sending a response doesn't automatically stop the function execution. +1. If the controller finds the author, it sends a response with a 200 status code and the found author object using `res.send(...)`. Very simple right? ### Handling errors -When building robust applications, it's crucial to handle errors gracefully within your application. By implementing proper error handling, you can provide meaningful error responses to the client and prevent your application from crashing unexpectedly. And as we know, errors may occur during async operations, and we are not exactly handling that in our previous code snippets. Let's see what we can do to handle these possibilities. +When building robust applications, it's crucial to handle errors gracefully within our application. Implementing proper error handling allows you to provide meaningful error responses to the client and prevent your application from crashing unexpectedly. And as we know, errors may occur during async operations, and we are not exactly handling that in our previous code snippets. Let's see what we can do to handle these possibilities. #### try/catch -Using the same code earlier, wrapping your controller logic in a `try/catch` is the quickest way to handle errors. +Using the same code from earlier, we can quickly handle errors by wrapping our controller logic in a `try/catch` block. ```javascript -// user controller file - controllers/userController.js - -const getUserById = async (req, res) => { - const userId = req.params.id; +const getAuthorById = async (req, res) => { + const { authorId } = req.params; try { - const user = await someDBQueryToGetUser(userId); + const author = await getAuthorById(authorId); - if (!user) { - res.status(404).send("User not found"); + if (!author) { + res.status(404).send("Author not found"); return; } - res.send(`User found: ${user.name}`); + res.send(author); } catch (error) { - console.error("Error retrieving user:", error); + console.error("Error retrieving author:", error); res.status(500).send("Internal Server Error"); // or we can call next(error) instead of sending a response here @@ -186,35 +210,35 @@ const getUserById = async (req, res) => { }; ``` -However, this is not exactly a clean solution since eventually, we will have to add the same try/catch block to *all* controllers. There is a package called [express-async-handler](https://www.npmjs.com/package/express-async-handler) that you can install to hide this bit. +However, this is not exactly a clean solution since eventually, we will have to add the same try/catch block to *all* controllers. We can install a package called [express-async-handler](https://www.npmjs.com/package/express-async-handler) to hide this bit of code. ```javascript const asyncHandler = require("express-async-handler"); -// Any errors that is thrown in this function will automatically be caught and call the `next` function -const getUserById = asyncHandler(async (req, res) => { - const userId = req.params.id; +// The function will automatically catch any errors thrown and call the next function +const getAuthorById = asyncHandler(async (req, res) => { + const { authorId } = req.params; - const user = await someDBQueryToGetUser(userId); + const author = await getAuthorById(authorId); - if (!user) { - res.status(404).send("User not found"); + if (!author) { + res.status(404).send("Author not found"); return; } - res.send(`User found: ${user.name}`); + res.send(author); }); ```
-The asyncHandler function in the express-async-handler function is just 6 lines of code. try to take a guess on how it's implemented and then do [check out the source code](https://github.com/Abazhenov/express-async-handler/blob/master/index.js) for yourself. +The asyncHandler function in the `express-async-handler` library is just 6 lines of code. Try to take a guess on how it's implemented and then do [check out the source code](https://github.com/Abazhenov/express-async-handler/blob/master/index.js) for yourself.
#### With a middleware -Remember what we said earlier regarding a "special type of middleware"? Let's actually look into that now. There is an error middleware function that handles all errors in our application coming down from other middleware functions and this error middleware function is commonly placed at the very end of our application code, to ensure that it is actually the last middleware function to be executed and to only handle errors bubbling down from other middleware functions before it. +Remember our earlier discussion about a "special type of middleware"? Let's dive into that now. An error middleware function handles all errors in our application that come down from other middleware functions. We commonly place this error middleware function at the very end of the application code. This placement ensures it's the last middleware function executed and only handles errors bubbling down from preceding middleware functions. ```javascript // Every thrown error in the application or the previous middleware function calling `next` with an error as an argument will eventually go to this middleware function @@ -224,13 +248,13 @@ app.use((err, req, res, next) => { }); ``` -However, take note that this is a middleware function that requires *four parameters* that you will need to provide even if they are not used. If you for example exclude one of the parameters, it will not be recognized as an error middleware function. You can try it out yourself ;) +However, take note that this is a middleware function that requires *four parameters* that we will need to provide even if they are not used. If for example we exclude one of the parameters, it will not be recognized as an error middleware function. You can try it out yourself ;) -It is the same as the previous middleware functions with three parameters but with one new parameter in a different order which is the error object. This is an odd one but the error object *must* be the first parameter in the callback. +It is the same as the previous middleware functions with three parameters but adds a new parameter - the error object - in a different order. This is an odd one but the error object *must* be the first parameter in the callback. This middleware function handles the *errors thrown* in other middleware functions or something that is sent by a previous middleware function using the `next` function (e.g. `next(err)`). -So the way Express distinguishes this middleware function is again through adding *four parameters* not a single one missing. A route middleware function or a middleware function with *less than four parameters* will always be considered as a request middleware function instead of this error middleware function even if you place it last. +So the way Express distinguishes this middleware function is again through adding *four parameters* not a single one missing. A route middleware function or a middleware function with *less than four parameters* will always be considered as a request middleware function instead of this error middleware function even if we place it last. ```javascript app.use((req, res, next) => { @@ -249,7 +273,11 @@ app.use((err, req, res, next) => { With the solutions above, the error middleware function can only really respond with a `500` status code no matter what error it is. But what if we actually want to send a `404`? A common way to do this is to create our own custom error by extending the Error object. +Create the following file: + ```javascript +// errors/CustomNotFoundError.js + class CustomNotFoundError extends Error { constructor(message) { super(message); @@ -258,21 +286,25 @@ class CustomNotFoundError extends Error { this.name = "NotFoundError"; } } + +module.exports = CustomNotFoundError; ``` -We can then use this custom error class and refactor the earlier version of `getUserById` like so: +We can then use this custom error class and refactor the earlier version of `getAuthorById` like so: ```javascript -const getUserById = asyncHandler(async (req, res) => { - const userId = req.params.id; +const CustomNotFoundError = require("../errors/CustomNotFoundError"); + +const getAuthorById = asyncHandler(async (req, res) => { + const { authorId } = req.params; - const user = await someDBQueryToGetUser(userId); + const author = await getAuthorById(authorId); - if (!user) { - throw new CustomNotFoundError("User not found"); + if (!author) { + throw new CustomNotFoundError("Author not found"); } - res.send(`User found: ${user.name}`); + res.send(author); }); ``` @@ -288,7 +320,7 @@ app.use((err, req, res, next) => { }); ``` -This is a useful pattern and you will likely create more custom error classes for different use cases. +This is a useful pattern and we will likely create more custom error classes for different use cases. #### What is the next function @@ -326,62 +358,58 @@ Also, as we've discussed earlier with regards to calling the `next` function. We 1. With the string `next('route')` - Will pass control to the next route handler with the same matching path (if there is one). This only works for `app.METHOD` or `router.METHOD`. Potentially, it can also be the same as just calling `next` with no argument. 1. With the string `next('router')` - Will skip all middleware functions attached to the specific router instance and pass control back out of the router instance. Basically, we exit the router and go back to the parent router, e.g. `app` (yes, the Express app is also just a router under the hood). -Out of the four, you will likely only use the first two, unless you have a very specific need that requires the other two. +Out of the four, we will likely only use the first two, unless we have a very specific need that requires the other two. ### Consolidating what we've learned -The following is the folder structure you'd end up with if you have been coding along: +The following is the folder structure we'd end up with if you have been coding along: ```text express-app/ +├─ errors/ +│ ├─ CustomNotFoundError.js ├─ controllers/ -│ ├─ userController.js +│ ├─ authorController.js ├─ routes/ -│ ├─ userRoutes.js +│ ├─ authorsRouter.js + ├─ ... ├─ app.js +├─ db.js ``` -Creating a user controller file that contains well-defined responsibilities to handle "User". +Creating a controller file that contains well-defined responsibilities to handle "Author". ```javascript -// user controller file - controllers/userController.js +// controllers/authorController.js const asyncHandler = require("express-async-handler"); -const getUserById = asyncHandler(async (req, res) => { - const userId = req.params.id; +const getAuthorById = asyncHandler(async (req, res) => { + const { authorId } = req.params; - const user = await someDBQueryToGetUser(userId); + const author = await getAuthorById(authorId); - if (!user) { - throw new CustomNotFoundError("User not found"); + if (!author) { + throw new CustomNotFoundError("Author not found"); } - res.send(`User found: ${user.name}`); -}); - -const getUsers = asyncHandler(async (req, res) => { - // code + res.send(author); }); -const createUser = asyncHandler(async (req, res) => { - // code -}); - -module.exports = { getUserById, getUsers, createUser }; +module.exports = { getAuthorById }; ``` -Route file for the "User" +The route file for the "Author" ```javascript -// route file - routes/userRoutes.js +// routes/authorsRouter.js const express = require('express'); -const userController = require(''); +const authorController = require('../controllers/authorController'); const router = express.Router(); -// You can for example add a top level middleware function that handles say authentication and only let the request come in if they're authenticated +// We can for example add a top level middleware function that handles say authentication and only let the request come in if they're authenticated // This prevents from executing the middleware functions below if the request is not authenticated // We will learn more about authentication in later lessons // usually calls either next() or next(error) @@ -390,49 +418,78 @@ const router = express.Router(); // router-level middlewares -// GET request for getting all the users -router.get('/', userController.getUsers); +// GET request for getting all the authors +router.get('/', /* Try to implement the controller for getting all authors */); -// You will likely place your validation/authentication middleware functions here or perhaps in the controller file, e.g. -// router.post(validationMiddleware, userController.createUser) +// We will likely place our validation/authentication middleware functions here or perhaps in the controller file, e.g. +// router.post(validationMiddleware, /* Try to implement the controller for creating an author */); -// POST request for creating a user -router.post('/', userController.createUser); +// POST request for creating a author +router.post('/', /* Try to implement the controller for creating an author */); -// GET request for getting the user by id -router.get('/:id', userController.getUserById); +// GET request for getting the author by id +router.get('/:authorId', authorController.getAuthorById); module.exports = router; ``` +The mock db file. + +```javascript +// db.js + +const authors = [ + { id: 1, name: "Bryan" }, + { id: 2, name: "Christian" }, + { id: 3, name: "Jason" }, +]; + +const getAuthorById = async (authorId) => { + return authors.find(author => author.id === authorId); +}; + +module.exports = { getAuthorById }; +``` + The main application file, our entry point. ```javascript -// app file +// app.js -const express = require('express'); -const userRouter = require(''); +const express = require("express"); +const authorsRouter = require("routes/authorsRouter"); +// other imports const app = express(); -// application-level middlewares, will always execute on every incoming requests +// We can also add application-level middlewares, which will always execute on every incoming request // parses form payloads and sets it to the `req.body` app.use(express.urlencoded({ extended: false })); app.use((req, res, next) => { - // You can of course also create your own for your own use-case! + // We can of course also create our own for our own use-case! // Just make sure to call `next` next(); }) -// base mount path is `/users` and will always execute on that specific mount path, and yes including `/users/a/b/c` -app.use('/users', userRouter); +// base mount path is `/authors` and will always execute on that specific mount path, and yes including `/authors/a/b/c` +app.use("/authors", authorsRouter); +// other routes + +app.use((err, req, res, next) => { + console.error(err); + res.status(err.statusCode || 500).send(err.message); +}); const PORT = 3000; -app.listen(PORT, () => console.log(`listening on port ${PORT}!`)); +app.listen(PORT, () => { + console.log(`My first Express app - listening on port ${PORT}!`); +}); ``` +To add more practice, feel free to create more mocks in our `db.js` file and controllers for the other routes we've defined in the `routes` directory in the previous lesson. + ### Assignment
@@ -447,11 +504,12 @@ app.listen(PORT, () => console.log(`listening on port ${PORT}!`)); The following questions are an opportunity to reflect on key topics in this lesson. If you can't answer a question, click on it to review the material, but keep in mind you are not expected to memorize or master this knowledge. - [What are the common response methods and their use cases?](#handling-responses) -- [What are the types of middlewares?](#middleware) +- [What are the types of middleware?](#middleware) - [How does a middleware get executed? In which order?](#middleware) - [What does calling the next function do? What if we pass an error argument?](#what-is-the-next-function) -- [What other arguments you can pass to the next function?](#what-is-the-next-function) +- [What are the other arguments you can pass to the next function?](#what-is-the-next-function) - [What is a controller?](#controllers) +- [What is the difference between a controller and a middleware?](#controllers) - [What happens if you define a middleware function with four parameters?](#with-a-middleware) - [What would you do to create a custom error?](#creating-custom-errors)