ExpressJS Advanced Topics#
Middleware#
- In ExpressJS we already mentioned about the Middleware. So, basically the Middleware function is basically a function that takes a request object, and either returns a response to the client, or passes control to another Middleware function.
- So in
express
, every route handler function we have is technically a Middleware function, because it takes a request option, and in this case it returns a response to the client, so it terminates the request response cycle.
- Express includes a few built in Middleware functions, but we can also create custom Middleware functions that we can put at the front of our request processing pipeline. So every request that we get on the server, will go through our Middleware function, but this custom Middleware function, we can perform cross cutting concerns. For example, we can do logging, authentication, authorization, and so on.
Create A Custom Middleware#
- Before beginning with the example, we will clone the existing example
express-demo
in ExpressJS and name it asexpress-demo-middleware
. - To use a Middleware function into
Express
we need to use the methoduse
. For example.
1 2 |
|
-
In Express, the
use()
method is a core function that is used to apply middleware to the application's request-response cycle.-
Add Middleware Globally: We can use
app.use()
to add middleware that will be executed for every request made to the application. This is often used for tasks like logging, authentication, parsing request bodies, setting headers, and more. -
Add Middleware to Specific Routes: We can use
app.use()
with a specific path to apply middleware only to requests that match that path. For example,app.use('/api', middlewareFunction)
would apply the middleware only to routes that start with/api
. -
Chain Multiple Middleware Functions: We can chain multiple middleware functions together using multiple
app.use()
calls, which will execute in the order they are added.
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
-
Creating a Middleware function in Express involves defining a JavaScript function that takes three arguments:
req
(request),res
(response), andnext
. Thenext
argument is a function that, when called, passes control to the next middleware in the stack. Ifnext
is not called, the request-response cycle will not proceed beyond this middleware. -
Okay, let's start with simple global Middleware functions. Let's create 2 files:
logger.js
andauthentication.js
with the content as below in ourexpress-demo-middleware
.
logger.js | |
---|---|
1 2 3 4 5 6 |
|
authentication.js | |
---|---|
1 2 3 4 5 6 |
|
- As we can see, two methods above contains 3 arguments
req
,res
andnext
and we will callnext()
at the end of the function to make sure we pass the control to the other Middleware. - Next, we will import those Middleware functions and use them as in the code below.
index.js | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
|
- Now, let's start our node application and try to call any api, then we can see the Middleware functions are triggered.
1 2 3 4 5 6 7 8 9 |
|
Built-In Middleware#
- In express we have a few built in Middleware functions. Below is the table contains all the built-in Middleware functions of Express.
Middleware Function | Description |
---|---|
express.json() |
Parses incoming JSON payloads and makes the parsed data available on req.body . |
express.urlencoded() |
Parses incoming URL-encoded payloads and makes the parsed data available on req.body . |
express.text() |
Parses incoming request payloads as plain text and makes the data available on req.body . |
express.raw() |
Parses incoming request payloads as buffers and makes the data available on req.body . |
express.static() |
Serves static files (e.g., HTML, CSS, images) from a specified directory. |
express.Router() |
Creates a modular route handler using the Router class. |
express.cookieParser() |
Parses Cookie header and populates req.cookies with an object of cookie key-value pairs. |
express.session() |
Adds session support, allowing you to store user data across requests. |
express.csrf() |
Implements Cross-Site Request Forgery (CSRF) protection by generating and validating tokens. |
express.logger() |
Logs information about incoming requests, including method, URL, response status, and response time. |
express.compress() |
Compresses response data before sending it to the client to reduce transfer size. |
express.timeout() |
Sets a maximum request processing time, terminating requests that exceed the specified duration. |
express.errorHandler() |
Handles errors and sends appropriate error responses. |
express.cookieSession() |
Provides session support by storing session data in cookies. |
express.bodyParser() |
Parses incoming request bodies (deprecated in favor of express.json() and express.urlencoded() ). |
express.methodOverride() |
Overrides HTTP methods (e.g., POST to PUT/DELETE) using the _method query parameter or X-HTTP-Method-Override header. |
express.basicAuth() |
Implements basic authentication for routes. |
express.query() |
Parses the query string and populates req.query with the parsed query parameters. |
express.responseTime() |
Adds an X-Response-Time header to responses indicating the response time in milliseconds. |
express.favicon() |
Serves a favicon (icon associated with a website) from a specified file. |
express.directory() |
Serves a directory listing of files in a specified directory. |
express.limit() |
Limits the size of incoming request bodies. |
express.vhost() |
Supports virtual hosting (multiple domains on a single server) by directing requests to different middleware based on the host value. |
express.jsonp() |
Responds to JSONP requests (cross-domain requests using the <script> tag) by wrapping the JSON response in a callback function. |
express.rawBody() |
Provides raw request bodies without parsing. |
express.sslify() |
Redirects HTTP requests to HTTPS for secure communication. |
express.xmlBodyParser() |
Parses incoming XML request payloads and makes the parsed data available on req.body . |
express.cors() |
Adds Cross-Origin Resource Sharing (CORS) headers to responses, allowing controlled sharing of resources across domains. |
- Now, let's take an example for using built-in middlewares, we will use
express.urlencoded()
andexpress.static()
methods as below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
|
- With
app.use(express.urlencoded({extended: true}));
we will parses incoming request payloads as plain text and makes the data available onreq.body
. - Next with
app.use(express.static('public'));
we will serve a static files (e.g., HTML, CSS, images) from a specified directorypublic
. - Let's create a folder
public
which contains a simpleindex.html
file as below.
index.html | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 |
|
- Then let's start the node application and use postman to test the
express.urlencoded()
built-in middleware as below.
- Then if we use browser and open
localhost:5000
we can see the static html file is loaded as below.
Third-Party Middleware#
- Third-party middleware in Express refers to middleware functions that are not built-in to the Express framework itself but are instead developed and maintained by the broader developer community. These middleware modules are created to handle specific tasks or functionalities that might be common in web applications, such as authentication, logging, data validation, security enhancements, and more.
- We can find all of third-party middleware at this official link of Express.
- Let's take an example by using some third-party middleware like Helmet and Morgan which are used for securing Express apps by setting HTTP response headers and logging HTTP Requests.
- Firstly, let's install the
Helmet
andMorgan
third-party middlewares as below.
1 2 |
|
- Then we use them in the
index.js
file as below.
index.js | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
|
- Now, let's start node application and call any api we will get the results as below.
- As you can see, with
Helmet
middleware we can see there are many security headers added into the response. - Then if we look into the node application log, we can see the api that we call had logged.
1 2 3 4 5 6 7 8 9 10 |
|
Environments#
- In a more complex, or enterprise like application, we need to know what environment our code is running on. Is this a development environment, or a production environment?Perhaps we may want to enable or disable certain features based on the current environment.
- We learned about this process object in Global Object, this global object gives us the way to access to the current process. This process object has a property called
env
, which gives us the environment variables. - Now we have a standard environment variable called
NODE_ENV
, and this environment variable returns the environment for this node application. If it's not set, here we're going to get undefined.
1 |
|
- Now we have another way to get the current environment, and that is for the app object. So this app object has a method called get that we use to get various settings about this application.
1 |
|
-
This method internally uses this environment variable to detect the current environment. However, if this environment variable is not set, this will return
development
by default. -
Okay let's add the log below into the
index.js
file.
1 2 |
|
index.js | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
|
- Now, when we start the application then we can see in the node application log 2 results
undefined
anddevelopment
as below.
1 2 3 4 5 6 7 8 9 |
|
- Then if we want to enable
morgan
only on the development machine. So we can write code like this.
index.js | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
|
- Now, let's start the node application then you can see the
Morgan
is enabled as below.
1 2 3 4 5 6 7 8 9 10 |
|
- Then now, let's set env as
test
then we can see theMorgan
is not enabled as below.
1 2 3 4 5 6 7 8 9 10 |
|
Configuration#
- Okay, now we will continue to learn how to store configuration settings for our node application and override them in each environment.
- There are many node packages for managing configuration and the most popular one is
config
. - The
config
provides a way to define configuration parameters for different environments (development, production, testing, etc.) and makes it easy to access these settings within our code. -
The
config
package allows use to:- Define configuration files in various formats (JSON, YAML, etc.) for different environments.
- Access configuration values based on a hierarchical structure that includes environment-specific overrides.
- Easily switch between different configurations based on the environment in which your application is running.
- Provide default values for configuration options.
- Keep sensitive data, such as API keys or database credentials, separate from your code.
-
Now, let's install
config
for using.
1 |
|
- After that, let's create a folder
config
for storing application configurations following environment. - In side the folder config, let's create 3 json files:
default.json
,development.json
andproduction.json
as below.
default.json | |
---|---|
1 2 3 |
|
development.json | |
---|---|
1 2 3 4 5 6 |
|
production.json | |
---|---|
1 2 3 4 5 6 |
|
- Then in the
index.js
, let's import theconfig
package, then to get the configuration in json files we just simply useconfig.get('<fieldName>')
as in the example below.
index.js | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
|
- As we can see, we will log out values that we get from json file using
config
. Let's start our node application withdevelopment
env then we can see the result as below.
1 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
- Then let's try with
production
env.
1 |
|
1 2 3 4 5 6 7 8 9 10 11 12 |
|
- Okay, please note that in the json configuration files, we should not put sensitive value such as username/password or credentials because when we're checking our source code, to a repository, that password or that secret is visible to anyone who has access to that source control repository. So to deal with it we let's store these secrets in environment variables.
- For example, we will export an
app_password
environment variable and we will use config module to read it.
1 |
|
- Then let's create a json file
custom-environment-variables.json
with the value is the environment name that we have just exported above.
custom-environment-variables.json | |
---|---|
1 2 3 4 5 |
|
- Next, let's go back to the
index.js
and add a using the config module to get the environment variable throughcustom-environment-variable.json
as below.
index.js | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
|
- Now, let's reload our node application again then we can see the
app_password
that we set into the environment.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Debugging#
- When we work with nodejs, we usually use
console.log
for debug logging but this approach has some problems because not every time we will need to log out and usually we will remove it when we have done some code implementation and when we need it again we have to add theconsole.log
manually. It will take time for us. - So there is a better way is using
debug
node package. -
The
debug
package is a third-party npm package for debugging Node.js applications. It provides a flexible and convenient way to add debug logs to your codebase, allowing you to toggle these logs on and off based on environment variables. Thedebug
package is particularly useful for gaining insights into the behavior of your application during development and troubleshooting issues.- Namespaced Debugging: The
debug
package allows you to create debug instances with different namespaces. This makes it easy to categorize and control different parts of your application's debugging output. - Environment-Based Control: Debugging logs can be enabled or disabled based on environment variables. This means you can keep the debug logs in your code and control their visibility without having to modify the code itself.
- Color-Coded Output: The
debug
package provides color-coded output for better readability of debugging messages. - Configurable Output Streams: You can configure where the debugging logs are sent. By default, they are sent to the console, but you can redirect them to other streams, such as files.
- Namespaced Debugging: The
-
Now, firstly let's create another project name
express-demo-debug
with thepackage.json
as below.
package.json | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
- Then, let's create a simple
index.js
file as below.
index.js | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 |
|
- Then, let's install the
debug
node package using the command below.
1 |
|
- Okay, let's create debug instances with namespaces. To create an instance with a namespace we will use the syntax as below.
1 |
|
- In the example below we will create 2 namespaces
app:startup
andapp:db
and use them for logging information.
index.js | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
- Next, we will set environment variable to decide which namespace we will use for debugging in our node application. For example we will use the namespace
app:startup
. - So let's export an environment variable
DEBUG
with value is the namespaceapp:startup
1 |
|
- Then let's start our node application then you can see the result as below.
1 2 3 4 5 6 7 8 9 |
|
- As you can see, only the log with namespace
app:startup
is logged out. - Let's try to set the
DEBUG
environment with valueapp:db
and start the node application again.
1 |
|
1 2 3 4 5 6 7 8 9 |
|
-
Again, only the log with namespace
app:db
is logged out. -
Okay then, if we want to apply multiple namespace we can set the value for
DEBUG
environment as below.
1 |
|
- We will use the
,
between namespaces.
1 2 3 4 5 6 7 8 9 10 |
|
- In case, we want to apply all the namespaces we can set like this.
1 |
|
1 2 3 4 5 6 7 8 9 10 |
|
- Now there's also a faster way to set the level of debugging we want to see, so we don't have to explicitly set the environment variable, using the export command. We can set the environment variable at the time of writing our application using the command below.
1 |
|
1 2 3 4 5 6 7 8 |
|
Templating Engines#
- A templating engine in Node.js is a tool that helps generate dynamic HTML content by combining templates and data. It allows us to create reusable templates with placeholders that are replaced with actual data when the template is rendered. This is particularly useful for generating web pages, emails, or any other content where you need to display dynamic data.
- There are various templating engines available for express applications. The most popular ones are
Pug
, which used to be calledJade
. We also haveMustache
, andEGS
. - Okay now, let's take an example with
Pug
. - Firstly, let's clone project
express-demo-middleware
and name it asexpress-demo-pug
. - Next, let's install
Pug
as in the command below.
1 |
|
- Next, let's create a folder
views
with apub
fileindex.pug
as below.
index.pug | |
---|---|
1 2 3 4 5 |
|
- The title element's content is dynamically set using the Pug variable title. The = syntax denotes interpolation, meaning the value of title will be inserted into the HTML.
-
Inside the
<body>
, there is anh1
element. Similar to before, theh1
element's content is dynamically set using the Pug variablemessage
. -
Then let's configure
express
to usepug
as in the code below.
1 2 |
|
- Then let's update the default api
/
which will return an html file with the response as below.
1 2 3 |
|
- As you can see, the first argument contains
index
value, it is the name ofpug
file in folderviews
. The second argument is the object that containkey-value
pairs in which the key set inindex.pug
.
index.js | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
|
- Now, let's start our node application and go to browser, we will see the result as below.
Structuring Express Applications#
- In a real world application we don't want to write all that code inside
index.js
. So in this lecture, we will learn how to structure our application. - Firstly, let's clone the project
express-demo-pug
to new projectexpress-demo-structure
. - Then we will restructure the apis, let's create a folder
routes
and create a js file base on the api. For example in theindex.js
we have/api/courses
apis. So we will createcourses.js
file inroutes
folder as below.
routes/course.js | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
|
- As you can see, we will use
express.Router()
to create a router object and we don't need to add/api/courses
for every api again, we will do it inindex.js
. At the end of this file we will export the router object. - We will do the same for home api
/
with filehome.js
as below.
home.js | |
---|---|
1 2 3 4 5 6 7 8 |
|
- Next, as we remember that in the
index.js
we have used a middlewares namelogger
andauthentication
fromlogger.js
andauthentication.js
files respectively. So let's create a foldermiddleware
and move thesejs
files there. - Finally in the
index.js
, we will remove all apis, we just need to import routes and middlewares and use them.
index.js | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
|
- As you can see, for the imported routes. We will define the path for specific route that we imported.
index.js | |
---|---|
1 2 3 4 |
|
- Let's start our node application again then we can see it can run normally.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
- Okay, that's all we will learn about connecting to MongoDb and setup authentication in next lectures.