Having looked at modules, we will now look at how to implement an Express router as a module.
In a larger application, you will quickly find that your main Express file (often app.mjs
) will become very large, handling a large number of routes. What you can do instead is to define a Router. A router allows you to set up a group of routes which match a particular path. For example we might create a router matching the path /products which will handle any routes beginning with /products, e.g: /products/all, /products/id/:id and so on. To do this we create a routes folder within the folder containing our main Express server, and place a route handler in there, which would be saved as a Node module (we looked at modules in topic 5. Here is an example router module (product.mjs) which we would save in our routes folder:
// routes/product.mjs import express from 'express'; const productRouter = express.Router(); productRouter.get('/all', (req,res)=> { // code to return all products }); productRouter.get('/id/:id', (req,res)=> { // code to find the product with the given ID }); export default productRouter; // export the module for external useNote how we are making
productRouter
the default export from this module, which means we'll be able to import it as follows:
import productRouter from './routes/product.mjs';This is shown in the full Express application, below:
import express from 'express'; const app = express(); // Import our product router module which we created above import productRouter from './routes/product.mjs'; // Tell the app to use productRouter for all routes beginning with /products // As we are using "use()", the router is acting as a middleware - see below app.use('/products', productRouter); app.listen(3000);So the song router is imported from the routes/product module (corresponding to routes/product.mjs) and then we call use() with two arguments:
/products
;/products/all
will be handled via the product
router (as the route begins with /products
) and then via the /all
route within the products
router.
Last time you were introduced to middleware. However, we wrote our middleware in our main app.mjs
which is not ideal for reusability. We saw examples last week of standard middleware such as express.json()
so it is valuable to write middleware in its own modules so that it can be imported into other projects. As an example we will look at a piece of CORS middleware.
Here is a piece of middleware which adds a CORS header to all responses to allow clients from any domain to connect. In this example, the middleware is written directly inside app.mjs
.
app.use((req, res, next) => { res.set('Access-Control-Allow-Origin', '*'); next(); });Note how we specify a custom arrow function as the argument to
use()
. This is our middleware, and you can see that it takes req
, res
and next
parameters as described above. Note how our midleware adds the CORS Access-Control-Allow-Origin
header to the response object to allow any domain to connect, and then calls the next function in the middleware chain with next()
.
However, we can write the middleware as a separate function, in a module, as long as it has the required req
, res
and next
parameters. As we have seen, the advantage of this method is that your function can be placed inside a module and imported into other projects. Here is an example of writing our CORS middleware as a separate function in a module:
// corsMiddleware.mjs function corsMiddleware(req, res, next) { res.set('Access-Control-Allow-Origin', '*'); next(); } export default corsMiddleware;We could then
import
this from our main server, and use()
it, as follows:
import express from 'express'; const app = express(); // Require the middleware module import corsMiddleware from './corsMiddleware.mjs'; // use() it app.use(corsMiddleware); // Routes app.get('/', ...); // etc app.listen(3000);
Finally in this topic we will cover a useful Node module, dotenv
. The dotenv
module allows developers to specify environment variables - variables which can be set by the user as input to the application and may change - within a file, .env
. Examples of such variables might include database name, username and password. This also has a security advantage: by adding private data to your .env
it means that you can keep information such as passwords outside your source code, and publish your code to GitHub or another repository without giving away such information.
dotenv
is quite easy to use. Firsst of all, as it's a third-party module, you have to install it:
npm install dotenvYou then
import
it, for example below we import dotenv
after we have initialised our Express app object.
import express from 'express'; const app = express(); import 'dotenv/config';Environment variables are placed within the
.env
file as a series of key/value pairs. For example, here is a .env
file containing database settings:
DB_HOST=localhost DB_USER=root DB_DBASE=mysqlWe could then use these in our server via the
process.env
object. So we could use process.env.DB_USER
to access the database user, process.env.DB_HOST
to access the machine running the database, and process.env.DB_DBASE
to access the database name. For example, if we were using MySQL, which requires a host, username and password, we could do:
const con = mysql.createConnection({ host: process.env.DB_HOST, user: process.env.DB_USER, database: process.env.DB_DBASE });It's important to understand that you can only use
process.env
after you have imported dotenv
, as shown above.
One problem with our code so far is that we have had to initialise and load the database multiple times, once in the main app, and once per router. This is clearly inefficient and wasteful. What we can do instead is to create a module to initialse and export the database connection:
// db.mjs - initialsie and export the database connection object import Database from 'better-sqlite3'; const db = new Database("wadsongs.db"); export default db;As the database object is exported, we could then import it from every file which needs to use it, either the main application or any router:
import db from './db.mjs';or, from a router:
import db from '../db.mjs';
The exercise will allow you to practise with routers, middleware and dotenv
.
IMPORTANT: This is a slightly more advanced topic, so you need to ensure you have completed the sessions and logins exercise from the previous topic.
users.mjs
, should create an express.Router()
as shown in the second example, and should contain the three routes (/login
POST, /login
GET and /logout
)./users
, as shown in the example.import
it and use()
it in the main server file by specifying the name of the function exported from the module.songs.mjs
. In the same way that you did for your users
router, include this router under the top-level route /songs
, and test it by searching for all songs by a particular artist by requesting the correct URL in your browser.dotenv
and import
it in your main server application. Store the database name (e.g. wadsongs.db
) in a .env
file and alter your code so that it reads the database name from .env
.