πŸš€ Structuring Express.js for Clean REST API Design

Rahul SahRahul Sah
3 min read

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 resource

  • Proper 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

OperationHTTP MethodStatus Code
Get All UsersGET200 OK
Create UserPOST201 Created
Update UserPUT200 OK
Delete UserDELETE200 OK
  • Always return clear success flags and meaningful messages.

πŸ“ Diagram: CRUD Operations Mapping

ActionHTTP MethodEndpoint
CreatePOST/api/users
ReadGET/api/users
UpdatePUT/api/users/:id
DeleteDELETE/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.

0
Subscribe to my newsletter

Read articles from Rahul Sah directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Rahul Sah
Rahul Sah