Skip to content

Express JS

Aakash Goplani edited this page Feb 8, 2020 · 17 revisions

Topics Covered


Express JS Intro

  • 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 pass app here to create server.
      const server = http.createServer(app);

Middleware

  • 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.

middleware

  • More reading:

  • Using Middleware:

    • To define a middleware function we call the use() method on our app

      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 this use 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 calling next(), this will result in error!


Listening to Request and Sending Response

  • Express has send() method to send response to end user. The send() 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.
  • 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');

Handling different routes

  • 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

Parsing incoming requests

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 on bodyParser and you should pass the config options here and set extended to false. This function registers a middleware, which call next() 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.


Conditional implementation of Middleware

  • Like app.use(), which is generic, we also have app.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('/');
    });

Express Routes

  • 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, a get() 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.


Add 404 Page

  • 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>
       `);
    });

Filter Path

  • 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) => {
     ...
    });

Serving HTML pages

  • Instead of sending some text as response using send() method, we can send file as a response using sendFile() method. It also automatically sets the contentType 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 of join() 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 use path.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.


Navigation Utility

  • 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 has mainModule property that refers to module that started your application, that mainModule has filename 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.

Serving static files

  • 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 to app.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 a static() 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 the public folder and therefore then the remaining path has to be everything but that /public
  • 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.

Clone this wiki locally