Skip to content

Instantly share code, notes, and snippets.

@kezzico
Last active October 16, 2023 21:51
Show Gist options
  • Save kezzico/425ee061d127315bcd8d9ad9165145bb to your computer and use it in GitHub Desktop.
Save kezzico/425ee061d127315bcd8d9ad9165145bb to your computer and use it in GitHub Desktop.

Node.js: Express MVC

Link to sample code

Designing a Node.js/Express API following the Model-View-Controller (MVC) architectural pattern utilizing middleware is a common and effective approach.

Express.js is a popular Node.js web application framework known for its flexibility and simplicity.

A core concept that underlies much of its power and versatility is middleware.

Express.js middleware is a powerful tool for building robust and modular web applications.

By breaking down your application's logic into smaller, reusable functions, you can create a fluid and organized chain of processing for incoming requests.

Whether it's handling authentication, connecting to a database, or rendering views, middleware plays a central role in shaping the behavior of your Express application.

In this guide, we'll walk through creating a simple MVC API using Express and discuss how middleware fits into this design.

MVC Overview

The MVC pattern separates your application into three core components:

  1. Model: Represents the data and business logic of your application. In a Node.js API, this often corresponds to the data models or the database interaction layer (e.g., using an ORM like Sequelize or Prisma).

  2. View: Handles the presentation layer, typically responsible for rendering HTML or other response formats. In an API context, this can be thought of as the response format, such as JSON or XML.

  3. Controller: Serves as an intermediary between the Model and View. Controllers receive HTTP requests, interact with the Model to retrieve or manipulate data, and then render the appropriate View or send a response to the client.

Setting Up the Project

Let's start by setting up the project structure:

project-root/
  |- controllers/
  |   |- userController.js
  |
  |- models/
  |   |- userModel.js
  |
  |- views/
  |   |- profile.ejs
  |
  |- app.js
  |- package.json
  • controllers: Contains controller functions that handle HTTP requests and responses.
  • models: Defines data models and interacts with the database.
  • views: Defines HTML templates and binds them to data.
  • app.js: Initializes Express, sets up middleware, and handles dependency injection.

The MVC architectural pattern divides an Express.js application into distinct components, each with its own responsibility.

This structure promotes modularity, separation of concerns, and maintainability in your Node.js/Express.js project.

Middleware in Express

Middleware functions can be used to perform tasks such as authentication, request validation, logging, and more. Here's how you can integrate middleware into your Express application:

const userModel = require("./models/userModel")(dbpool)
const userController = require("./controllers/userController")(userModel)

app.listen(3000, () => { 
    app.use(cookieParser())

    app.use((req, res, next) => {
        console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
        next();
    });
      
    app.post("/user/login", userController.checkpassword)

In this example:

  • cookieParser() middleware parses cookie data from incoming requests.
  • A custom middleware logs each incoming request.
  • Routes for user-login operations are defined in userController.js.

One of the key strengths of Express is its ability to create a chain of middleware functions. Each middleware function can perform specific tasks, such as authentication, logging, or data validation. These functions are executed in the order they are defined, creating a pipeline for processing incoming requests.

For example, you might have a JWT token middleware that authenticates users based on JSON Web Tokens. This middleware can check the token in the request header and determine whether the user has the necessary privileges to access a particular route.

If the authentication is successful, it can pass the request to the next middleware in line.

Implementing MVC Components

Middleware is not limited to handling authentication or request logging; it can also interact with databases. Suppose you're using a database system like Sequelize ORM or writing raw SQL queries. In that case, you can create a database middleware that establishes database connections, handles queries, and manages transactions.

This separation of concerns keeps your code organized and maintainable.

In this scenario, the middleware serves as a controller, orchestrating interactions between the database and the application logic.

It ensures that data retrieval and manipulation are handled consistently throughout your application.

Model (userModel.js)

Define your data model and database interactions using a library like Sequelize or a database connector.

const Sequelize = require('sequelize');

const sequelize = new Sequelize('database', 'username', 'password', {
  dialect: 'mysql',
});

const User = sequelize.define('User', {
  username: Sequelize.STRING,
  email: Sequelize.STRING,
  // other fields...
});

module.exports = User;

Controller (userController.js) The middleware serves as a controller, orchestrating interactions between the database and the application logic.

It ensures that data retrieval and manipulation are handled consistently throughout your application.

Create controller functions to handle HTTP requests and interact with the Model.

const User = require('../models/userModel');

exports.getAllUsers = async (req, res) => {
  try {
    const users = await User.findAll();
    res.json(users);
  } catch (error) {
    res.status(500).json({ error: 'Internal Server Error' });
  }
};

// Other controller functions (e.g., createUser, updateUser, deleteUser)

By following the MVC architectural pattern and incorporating middleware into your Express API, you can create a well-structured and maintainable project. Controllers handle the logic, Models interact with the database, middleware takes care of the routing.

This separation of concerns allows for scalability and ease of maintenance as your API grows.

Each link in the middleware chain is a re-usable piece of functionality.

Remember that middleware is your friend. It enables you to keep your codebase clean, maintainable, and highly customizable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment