-
Notifications
You must be signed in to change notification settings - Fork 1
Express JS
- Express JS- Intro
- Middleware
- Listening to Request and Sending Response
- Handling different routes
- Parsing incoming requests
- Conditional implementation of Middleware
- Express Routes
- Add 404 Page
- Filter Path
- Serving HTML pages
- Navigation Utility
- Serving static files
-
Express is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications.
-
Install:
npm install --save express
(as production dependency) -
Import and Initialize:
const express = require('express'); const app = express();
- The express package exports a function and therefore we execute it and this will initialize a new object, you can create an express application and store it in a constant named
app
. The framework will store and manage a lot of things for us behind the scenes. - Now the
app
here actually also happens to be a valid request handler, so you can passapp
here to create server.const server = http.createServer(app);
- The express package exports a function and therefore we execute it and this will initialize a new object, you can create an express application and store it in a constant named
-
Expressjs is all about middleware. Middleware means that an incoming request is automatically funneled through a bunch of functions by expressjs, so instead of just having one request handler, you will actually have a possibility of hooking in multiple functions which the request will go through until you send a response.
-
This allows you to split your code into multiple blocks or pieces instead of having one huge function that does everything and this is the pluggable nature of expressjs, where you can easily add other third party packages and add certain functionalities.
-
More reading:
-
Using Middleware:
-
To define a middleware function we call the
use()
method on ourapp
app.use((req, res, next) => { console.log("Inside middleware"); })
-
use()
accepts an array of request handlers which is simply a function that will be executed for every incoming request and this function will receive three arguments viz the request, the response object and next function. -
next
is actually a function that will be passed to thisuse
function by expressjs nad helps to travel on to the next middleware.app.use((req, res, next) => { console.log("Inside middleware 1"); next(); }) app.use((req, res, next) => { console.log("Inside middleware 2"); })
-
In the above example the output is "Inside middleware 1" followed by "Inside middleware 2". If we would have omit the
next()
in first middlware, we would NEVER reach the second middleware, the reason for that is that we have to call next here to allow the request to travel on to the next middleware in line. -
So it basically goes from top to bottom through that file, through all the middleware functions but only if we call next, if we don't call next it just dies!
-
You should call
next()
if you want to allow the request to go to the next function/middleware OR you should send a response, YOU SHOULD NOT DO BOTH i.e. sending response plus callingnext()
, this will result in error!
-
-
Express has
send()
method to send response to end user. Thesend()
method does couple of stuff behind the scenes:- it set default header of
text/html
, only if we haven't set it, otherwise it would leave our default. - it does a bunch of checks to see if we're using outdated versions of that function.
- it basically analyzes what kind of data you are sending. If it's a string data, it sets the
contentType
to html but only if we haven't set it yet, otherwise it would leave our default. If we have other values like a number, a boolean and so on, it would actually set it to binary or JSON data.
- it set default header of
-
We can pass app to that create server method but instead we can also just use app and call listen and this will do both these things for us, it calls
http.createServer()
and passes itself as handler.const server = http.createServer(handler); server.listen(4200, 'localhost'); /* CAN BE REPLACED BY */ const app = express(); app.listen(4200, 'localhost');
-
Middleware to handle url
/
app.use('/', (req, res, next) => { console.log("handling `/` requests"); });
- the first argument, which is optional, is path that we're looking for, this allows us to filter out certain requests.
- then we have the callback.
-
Middleware to handle url
/add-product
- we simply duplicate our middleware and place it BEFORE the first middleware (i.e. the one with
/
) and replace the first argument with the required path:app.use('/add-product', (req, res, next) => { console.log("handling `/add-product` requests"); });
- Now why before this middleware and not after it? Because the request goes through the file from top to bottom and if we don't call
next()
, it's not going to the next middleware. - The
/
middleware handles all the request that starts with/
,/add-product
also starts with/
. This means we'll never reach the middleware designed for/add-product
. - Only way to reach is to place
/add-product
above/
. - ORDERING of middleware in your file is very crucial
- if we have a middleware that should be applied to all requests, we would simply add it on top of all the other middlewares, otherwise middleware's should be placed keeping in mind the order of execution
- we simply duplicate our middleware and place it BEFORE the first middleware (i.e. the one with
Extract data from incoming requests
-
Middleware that sends data via POST request:
app.use('/add-product', (req, res, next) => { res.send(` <html> <head><title>Add Product</title></head> <body> <form method="POST" action="/product"> Product Name: <input type="text" name="productName"> <button type="submit">Submit</button> </form> </body> </html> `); });
-
Middleware that parses data from POST request
const bodyParser = require('body-parser'); app.use(bodyParser.urlencoded({extended: false})); app.use('/product', (req, res, next) => { console.log('Product Added: ', req.body.productName); res.redirect('/'); });
-
The important part here is we can place
/product
middleware prior or after/add-product
?- They won't clash because they have nothing in common regarding the path. -
To redirect the request you can manually set the status code and location header OR you can use
response.redirect()
method provided by express which internally sets status code and header for you!res.statusCode = 302; res.setHeader('Location', '/'); OR res.redirect('/');
-
Request provides
body
property which is raw in nature as it doesn't try to parse the incoming request body. To do that, we need to register a parser and we do that by adding another middleware and you typically do that before your route handling middlewares because the parsing of the body should be done no matter where your request ends up. -
Now for that we can install a third party package Body Parser,
npm install --save body-parser
, and we can import that.const bodyParser = require('body-parser');
-
We can call
urlEncoded({extended: false})
method onbodyParser
and you should pass the config options here and set extended to false. This function registers a middleware, which callnext()
internally in the end, so that the request also reaches our middleware but before it does that, it will do that whole request body parsing.
- Like
app.use()
, which is generic, we also haveapp.get()
,app.post()
,app.put()
,app.delete()
. These methods only get invoked if the incoming request is of their type, e.g.app.post()
only executes if incoming request is a POST request.app.get('/', (req, res, next) => { // EXECUTED ONLY IF INCOMING REQUEST IS GET }); app.post('/product', (req, res, next) => { // EXECUTED ONLY IF INCOMING REQUEST IS POST console.log('Product Added: ', req.body.productName); res.redirect('/'); });
-
One convenient feature offered by expressjs is the router. To create router we use
Router()
method.const router = express.Router();
-
This router is like a mini express app tied or pluggable into the main express app which we can export,
... module.exports = router;
-
The router can be used to define a
use()
function for all requests, aget()
function for get,post()
for post and so on...router.get('/add-product', (req, res, next) => { ... }); router.use(bodyParser.urlencoded({extended: false})); router.post('/product', (req, res, next) => { ... });
-
We can then import these routes in our main file
app.js
const adminRoutes = require('./routes/admin'); const shoppingroutes = require('./routes/shop'); ... app.use('/admin', adminRoutes); app.use(shoppingroutes);
Like before, the order matters. You should be very thoughtful with placements of middlewares.
-
The request goes from top to bottom so if it finds some middleware that handles it, it will go in there. But if we got no fitting middleware and we don't have one here, then we make it all the way to the bottom (of the file) and eventually we don't handle that request.
-
To send a 404 error page, we simply have to add a catch-all middleware at the bottom of our main file.
router.get('/add-product', (req, res, next) => { ... }); router.use(bodyParser.urlencoded({extended: false})); router.post('/product', (req, res, next) => { ... }); ... app.use('/', (req, res, next) => { res.status(404).send(` <html> <head><title>404</title></head> <body> <h2>Page Not Found</h2> <a href="/admin/add-product">Back to Shopping Page</a> </body> </html> `); });
-
Sometimes these outsourced routes have a common starting path, so let's say all the admin routes actually are triggered with
admin/add-product
router.get('/admin/add-product', (req, res, next) => { ... }); router.post('/admin/product', (req, res, next) => { ... });
-
If we have such a setup where our paths in such a router file start with the same part or with the same segment, we can take that segment out of route and then go to the
app.js
file or main routing file and add it here, which will act as a filter.app.use('/admin', adminRoutes);
-
Now only routes starting with
/admin
will go into the admin routes. Expressjs will also omit or ignore this/admin
part in the url when it tries to match these routes, so now/add-product
will match the/admin/add-product
route because/admin
was already stripped out/* ALL routes in router files changes to */ router.get('/add-product', (req, res, next) => { ... }); router.post('/product', (req, res, next) => { ... });
-
Instead of sending some text as response using
send()
method, we can send file as a response usingsendFile()
method. It also automatically sets thecontentType
response header field.router.post('/product', (req, res, next) => { res.sendFile(file_name); });
-
Now we just need to point at that file we want to send, the question is how does the path look like? The file is in the other (views) folder. Well we could try using
../views/fie_name
, this won't work. The reason for this is - the slash i.e./
, refers to our root folder on our operating system not to this project folder. -
So in order to construct the path to this directory, we can use a feature provided by nodejs, we can import the
path
core module and then send a file where we create a path with the help ofjoin()
method that yields us a path by concatenating the different segments.const path = require('path'); res.sendFile(path.join(__dirname, '../', 'views', 'add-product.html')); OR res.sendFile(path.join(__dirname, '..', 'views', 'add-product.html'));
-
The first segment passed here is a global variable made available by nodejs
__dirname
that holds the absolute path on our operating system to this project folder and now we can add a comma and simply add views here and then the third segment will be our file. -
Important: we just write the file name without any slash e.g.
add-product.html
because we usepath.join()
method detects the operating system you're running on and then it will automatically build the path in a way that works on both Linux systems and Windows systems. As the paths on both systems are different e.g. on Linux systems you have paths like/abc/pqr
and on Windows\abc\pqr
, so if you manually construct this with slashes, it would not run across OS.
-
We can create a utility that provides us the path for the file and we can then reference that path in our
sendFile()
method or other such operations.const path = require('path'); module.exports = path.dirname(process.mainModule.filename);
-
path.dirname()
returns the directory name of a path. -
process
is the global variable which hasmainModule
property that refers to module that started your application, thatmainModule
hasfilename
property that describes which file this module was spun up. Together they help in finding out which directory or for which file we want to get the directory name.
-
-
So put in other words, this gives us the path to the file that is responsible for the fact that our application is running and this file name is what we put into
dirname()
to get a path to that directory. -
With this we can import this file in our main
app.js
file and use that path to send file as response to user:const path = require('path'); const rootDirectory = require('./util/path'); ... res.sendFile(path.join(rootDirectory, 'views', 'add-product.html'));
- Note: we are omitting
../
here as entire path is constructed by our utility and we don't need to traverse path manually any further.
- Note: we are omitting
-
Now typically, you would have some css or js files somewhere and would point at them when your app gets served, all your files here are not accessible by your users.
-
If you ever tried to enter
http://localhost:4200/views/shop.html
, that will not work because this is simply accepted by express and it tries to find a route that matches this. It goes toapp.js
file and goes through each and every middleware function, and finally 404 gets invoked! -
For this to work we register a new middleware that handles static file requests. The
express
object has astatic()
method that serves static files. We just have to pass in a path to the folder which we want to serve statically, so basically a folder which we want to grant read access to.const express = require('express'); const app = express(); const path = require('path'); const rootDirectory = require('./util/path'); ... app.use(express.static(path.join(rootDirectory, 'public')));
-
In the main file we can have reference like:
<link rel="stylesheet" href="/css/main.css">
- Important: Note we don't have reference
/public/css/main.css
./public
in the beginning would be wrong. Express will take any request that tries to find some file that has extension.css
or a.js
files, and it automatically forwards it to thepublic
folder and therefore then the remaining path has to be everything but that/public
- Important: Note we don't have reference
-
You could register multiple static folders and it will funnel the request through all of them until it has a first hit for the file it's looking for.
app.use(express.static(path.join(rootDirectory, 'public'))); app.use(express.static(path.join(rootDirectory, 'public_2')));
-
This is now how we can serve files statically and you're not just limited to css and javascript files, you can also serve images and so on.