This Example will show you how to make a REST API using Express.js and MySQL.
The database of the example contains two tables: book and user. The user-table will be used to authorize the REST API users. So, in the exercise you will build a login system, so that the credentials will be checked based on the user.
In an Express application generated with the Express generator, the files inside the routes folder are not actual controllers, but they do play an important role in defining how the application responds to client requests.
These routes files primarily handle routing, meaning they define which function should be executed when a certain URL path is requested. In smaller applications, you might see the request-handling logic written directly in these files. However, in larger or more structured applications, it's a common best practice to separate the logic into controller files (usually placed in a controllers folder), and have the route files simply call those controller functions.
So, to clarify:
routes/ → defines the routes and maps them to specific functions
controllers/ → contains the actual logic that runs when a route is accessed
Although the route files define the "entry points" of your app's functionality, the core business logic is typically delegated to controllers, making the architecture cleaner and more maintainable.
Note: In the examples on this website, we use a simplified structure where routes and controllers are combined in the same file, as seen in Examples/book_controller_mysql.php. This approach is suitable for learning purposes and smaller applications, making the code easier to understand without the complexity of multiple files.
To see how the book controller example would look when separated into routes and controllers folders, click the button below:
×
Separating Routes and Controllers
Here's how the book.js example can be split into separate routes and controllers files
following the MVC pattern. This structure is recommended for larger applications.
Benefits of Separation:
Better code organization and maintainability
Easier testing of business logic
Clearer separation of concerns (routing vs logic)
Reusable controller functions
📄 routes/book.js
const express = require('express');
const router = express.Router();
const bookController = require('../controllers/bookController');
// GET all books
router.get('/', bookController.getAllBooks);
// GET one book by id
router.get('/:id', bookController.getOneBook);
// POST new book
router.post('/', bookController.addBook);
// DELETE book by id
router.delete('/:id', bookController.deleteBook);
// PUT (update) book by id
router.put('/:id', bookController.updateBook);
module.exports = router;
Purpose: Defines routes and maps them to controller functions
📄 controllers/bookController.js
const book = require('../models/book_model');
// Get all books
exports.getAllBooks = function(request, response) {
book.getAll(function(err, dbResult) {
if (err) {
response.json(err);
} else {
console.log(dbResult);
response.json(dbResult);
}
});
};
// Get one book by id
exports.getOneBook = function(request, response) {
book.getOne(request.params.id, function(err, dbResult) {
if (err) {
response.json(err);
} else {
response.json(dbResult);
}
});
};
// Add new book
exports.addBook = function(request, response) {
book.add(request.body, function(err, dbResult) {
if (err) {
response.json(err);
} else {
response.json(dbResult);
}
});
};
// Delete book by id
exports.deleteBook = function(request, response) {
book.delete(request.params.id, function(err, dbResult) {
if (err) {
response.json(err);
} else {
response.json(dbResult);
}
});
};
// Update book by id
exports.updateBook = function(request, response) {
book.update(request.params.id, request.body, function(err, dbResult) {
if (err) {
response.json(err);
} else {
response.json(dbResult);
}
});
};
Purpose: Contains the business logic for handling requests