Overview
Express is the most widely used web framework for Node.js. This tutorial builds a complete REST API with CRUD endpoints, validation, error handling, and a clean project structure.
Prerequisites
- Node.js 18+ — Node.js official download
- Basic JavaScript familiarity
Step 1: Initialize the Project
mkdir express-api
cd express-api
npm init -y
npm install express
npm install -D nodemon
Add scripts to package.json:
"scripts": {
"start": "node src/server.js",
"dev": "nodemon src/server.js"
}
Step 2: Project Structure
express-api/
├── src/
│ ├── routes/
│ │ └── users.js
│ ├── middleware/
│ │ └── errorHandler.js
│ ├── data/
│ │ └── store.js
│ └── server.js
└── package.json
Step 3: Create the Server
// src/server.js
const express = require('express');
const usersRouter = require('./routes/users');
const errorHandler = require('./middleware/errorHandler');
const app = express();
app.use(express.json());
app.get('/health', (req, res) => res.json({ status: 'ok' }));
app.use('/api/users', usersRouter);
app.use((req, res) => res.status(404).json({ error: 'Not found' }));
app.use(errorHandler);
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server on port ${PORT}`));
Step 4: Build the Routes
// src/routes/users.js
const express = require('express');
const router = express.Router();
let users = [
{ id: 1, name: 'Alice', email: 'alice@example.com' }
];
let nextId = 2;
// List
router.get('/', (req, res) => {
res.json(users);
});
// Read one
router.get('/:id', (req, res, next) => {
const user = users.find(u => u.id === Number(req.params.id));
if (!user) return next({ status: 404, message: 'User not found' });
res.json(user);
});
// Create
router.post('/', (req, res, next) => {
const { name, email } = req.body;
if (!name || !email) {
return next({ status: 400, message: 'name and email are required' });
}
const user = { id: nextId++, name, email };
users.push(user);
res.status(201).json(user);
});
// Update
router.put('/:id', (req, res, next) => {
const user = users.find(u => u.id === Number(req.params.id));
if (!user) return next({ status: 404, message: 'User not found' });
const { name, email } = req.body;
if (name !== undefined) user.name = name;
if (email !== undefined) user.email = email;
res.json(user);
});
// Delete
router.delete('/:id', (req, res, next) => {
const index = users.findIndex(u => u.id === Number(req.params.id));
if (index === -1) return next({ status: 404, message: 'User not found' });
users.splice(index, 1);
res.status(204).end();
});
module.exports = router;
Step 5: Add Error Handling Middleware
// src/middleware/errorHandler.js
module.exports = (err, req, res, next) => {
const status = err.status || 500;
const message = err.message || 'Internal server error';
if (status >= 500) console.error(err);
res.status(status).json({ error: message });
};
HTTP Status Code Reference
| Code | Meaning | Used when |
|---|---|---|
| 200 | OK | Successful GET or PUT |
| 201 | Created | Successful POST |
| 204 | No Content | Successful DELETE |
| 400 | Bad Request | Validation failure |
| 404 | Not Found | Resource does not exist |
| 500 | Internal Server Error | Unhandled exception |
Testing the API with curl
# List users
curl http://localhost:3000/api/users
# Create a user
curl -X POST http://localhost:3000/api/users \
-H "Content-Type: application/json" \
-d '{"name":"Bob","email":"bob@example.com"}'
# Update
curl -X PUT http://localhost:3000/api/users/1 \
-H "Content-Type: application/json" \
-d '{"name":"Alice Smith"}'
# Delete
curl -X DELETE http://localhost:3000/api/users/1
Next Steps
- Replace the in-memory array with a database client such as
pgormongoose. - Add input validation with
zodorjoi. - Add authentication middleware using JWT or sessions.
- Add request logging with
morganorpino. - Write integration tests with
supertestandvitest.
