[Tutorial] How to Use MySQL or MariaDB with Node.js and Express

With a “model, view, controller” (MVC) structure you generally separate your code like described below. There’s an example on Github that I mentioned here.

The Router

This is a file, or group of files, that routes paths to controller functions.

When a request comes in to the server, it first hits the main file (app.js, server.js, or whatever you name it). That main file usually mounts a router or routers in it. Each section of the site can have a different router. Examples of site sections are things like “pages”, “foods”, “api”, etc.

Example of mounting some routers on the app:

// the root path gets sent to the `indexRouter`
app.use("/", indexRouter);
// the `/things` prefix gets sent to the `thingRouter`
app.use("/things", thingRouter); // or `foodRouter`
// the `/api` prefix gets sent to the `apiRouter`
app.use("/api", apiRouter);

The above code designates a router for each section of the site.

Inside the router files, paths in that section of the site are associated with functions from “controller” files.

Example of mapping paths to controller functions:

// import the `controllers/pageController.js` functionality
const ctrl = require("../controllers/pageController");

// Each path on this router calls a different function in the
// `pageController.js` file.
router.get("/", ctrl.homePage);
router.get("/about", ctrl.aboutPage);
router.get("/contact", ctrl.contactPage);

The Controller

A controller (file) guides requests to different functions on the site. It doesn’t define the layout or the data, but only connects the different parts of the site together.

The controller functions have two parameters: request and response.

The request holds information that the user has sent. Other information can be added to the request by middleware after it has arrived on the server, but you can basically think of it has “what the user wants”.

The response has functions on it to send things back to the user, like this:

// send JSON to the user
response.json({ message: "hello world" });

// send a `500` error to the user with JSON
response.status(500).json({ message: "error" });

// send HTML from a template to the user
response.render("templateName", dataObjectToInjectInTheTemplate);

So the controller basically takes in the request, figures out what the user wants, fetches it from other parts of the site (like the “model”, see below), and injects the information into a template before sending the rendered template back with the response.

The Model

The model files are where you can put your functions that query the database. The model files would export functions like getAllMonsters, getMonsterById, etc.

So the controller would get a request for a path like /api/3 (meaning get JSON for monster #3), and it could fetch the data from the model without needed to know how the data is queried or assembled.

// import database functions from the model
const monster = require("./models/monster.js");

// get a monster by ID (1, 2, or 3)
app.get("/api/:id", (req, res) => {
    // extract the :id parameter from the URL
    const id = req.params.id;

    // this function could be defined in `models/monster.js`
    monster.getMonsterById(id)
        .then(data => {
            if (data.length > 0) {
                console.log("data", data);
                res.json(data);
            } else {
                res.status(404).json({ message: "Not Found" });
            }
        })
        .catch(err => res.status(500).json(error));
});

The View

The view refers to files that control the display of the information, in this case Handlebars. Most logic is kept in the controllers, so the view files don’t turn into spaghetti code.

Summary

This is just a general idea, and there are other ways to do it.

  1. Main file – mounts different routers for each section
  2. Router files – map paths in sections to specific controller functions
  3. Controller files – take request and response, do some logic, then render a template or send JSON back to the browser
  4. Model files – the SQL queries go here, and the model files export functions that produce specific data, like getMonsterById that the controllers (and other parts of the site) can use.

If you keep everything separated by function, it makes things easier to change later. If the controller file doesn’t know anything about the data other than it can contain functions like getMonsterById, then you can completely change how that function works under the hood (like change the database type) without needing to rewrite your controllers or other parts of the site that depend on that data.

Other people here might post alternate ideas, but that’s at least how I set it up in my example repo. :slight_smile:

1 Like