π Structuring Express.js for Clean REST API Design

When building scalable backend APIs with Express.js, clean project structure and proper REST principles are key.
In this blog, weβll cover:
Folder structure for an Express project
REST API design using a
users
resourceProper status codes and response structure
Visual diagrams to make it simple
π Folder Structure for Clean Express API
Hereβs a simple but scalable structure:
project/
βββ controllers/
β βββ userController.js
βββ routes/
β βββ userRoutes.js
βββ models/
β βββ userModel.js
βββ app.js
βββ server.js
controllers/ β Handles request logic
routes/ β Manages API endpoints
models/ β Handles data (can be a DB model or simple array)
app.js β Sets up Express app
server.js β Starts the server
π¦ Basic REST API for "Users" Resource
Weβll build CRUD operations:
Create (POST) β Add user
Read (GET) β Get all users
Update (PUT) β Update user
Delete (DELETE) β Remove user
1. server.js
const app = require('./app');
const PORT = 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
2. app.js
const express = require('express');
const userRoutes = require('./routes/userRoutes');
const app = express();
app.use(express.json());
app.use('/api/users', userRoutes);
module.exports = app;
3. routes/userRoutes.js
const express = require('express');
const { getUsers, createUser, updateUser, deleteUser } = require('../controllers/userController');
const router = express.Router();
router.get('/', getUsers);
router.post('/', createUser);
router.put('/:id', updateUser);
router.delete('/:id', deleteUser);
module.exports = router;
4. controllers/userController.js
let users = [
{ id: 1, name: 'Rahul' },
{ id: 2, name: 'Sita' }
];
// GET all users
exports.getUsers = (req, res) => {
res.status(200).json({ success: true, data: users });
};
// POST create user
exports.createUser = (req, res) => {
const { name } = req.body;
const newUser = { id: users.length + 1, name };
users.push(newUser);
res.status(201).json({ success: true, data: newUser });
};
// PUT update user
exports.updateUser = (req, res) => {
const userId = parseInt(req.params.id);
const { name } = req.body;
users = users.map(user => (user.id === userId ? { ...user, name } : user));
res.status(200).json({ success: true, message: 'User updated' });
};
// DELETE user
exports.deleteUser = (req, res) => {
const userId = parseInt(req.params.id);
users = users.filter(user => user.id !== userId);
res.status(200).json({ success: true, message: 'User deleted' });
};
β Status Code Best Practices
Operation | HTTP Method | Status Code |
Get All Users | GET | 200 OK |
Create User | POST | 201 Created |
Update User | PUT | 200 OK |
Delete User | DELETE | 200 OK |
- Always return clear success flags and meaningful messages.
π Diagram: CRUD Operations Mapping
Action | HTTP Method | Endpoint |
Create | POST | /api/users |
Read | GET | /api/users |
Update | PUT | /api/users/:id |
Delete | DELETE | /api/users/:id |
π Diagram: Express API Request Flow
Client Request β Route (userRoutes.js) β Controller (userController.js) β Response
π Final Thoughts
Keep routes, controllers, and models separated for clean code.
Use correct HTTP status codes.
Always send consistent JSON responses.
Start with simple resources like
users
to practice clean API design.
Subscribe to my newsletter
Read articles from Rahul Sah directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
