Express REST API Tutorial for Node.js | Routing, Middleware & CRUD
이 글의 핵심
Hands-on Express REST API tutorial: hello server, routers, middleware, JSON CRUD, async error handling, and patterns you reuse in real Node.js services.
Introduction
What is Express.js?
Express.js is a fast, unopinionated web framework for Node.js.
Highlights:
- Small API surface for HTTP servers
- Middleware pipeline for cross-cutting concerns
- Flexible routing (paths, verbs, parameters)
- Template engines (EJS, Pug, etc.)
- Huge middleware ecosystem
Typical uses: REST APIs, server-rendered sites, microservices, proxies.
1. Install and hello server
npm init -y
npm install express
npm install --save-dev nodemon
// app.js
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello, Express!');
});
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Server: http://localhost:${PORT}`);
});
Basic skeleton
const express = require('express');
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.get('/', (req, res) => res.send('Home'));
app.get('/about', (req, res) => res.send('About'));
app.use((req, res) => {
res.status(404).send('Not found');
});
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Server error');
});
app.listen(3000, () => console.log('Listening on :3000'));
2. Routing
HTTP verbs
app.get('/users', (req, res) => res.json({ users: ['Alice', 'Bob'] }));
app.post('/users', (req, res) => {
const user = req.body;
res.status(201).json({ message: 'created', user });
});
app.put('/users/:id', (req, res) => {
const { id } = req.params;
res.json({ message: `updated ${id}`, body: req.body });
});
app.patch('/users/:id', (req, res) => {
const { id } = req.params;
res.json({ message: `patched ${id}`, body: req.body });
});
app.delete('/users/:id', (req, res) => {
const { id } = req.params;
res.json({ message: `deleted ${id}` });
});
app.all('/secret', (req, res) => res.send('Secret'));
Route parameters & query
app.get('/users/:id', (req, res) => {
res.send(`User id: ${req.params.id}`);
});
app.get('/users/:userId/posts/:postId', (req, res) => {
res.json(req.params);
});
app.get('/search', (req, res) => {
const { q, page = 1, limit = 10 } = req.query;
res.json({ q, page: parseInt(page, 10), limit: parseInt(limit, 10) });
});
Routers
// routes/users.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => res.json({ users: [] }));
router.get('/:id', (req, res) => res.json({ id: req.params.id }));
router.post('/', (req, res) => res.status(201).json({ ok: true }));
module.exports = router;
const usersRouter = require('./routes/users');
app.use('/api/users', usersRouter);
3. Middleware
function logger(req, res, next) {
console.log(`${req.method} ${req.url}`);
next();
}
app.use(logger);
app.use('/api', (req, res, next) => {
console.log('API prefix');
next();
});
Built-in
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static('public'));
app.use('/static', express.static('public'));
Popular packages
npm install cors morgan helmet compression
const cors = require('cors');
const morgan = require('morgan');
const helmet = require('helmet');
const compression = require('compression');
app.use(cors());
app.use(morgan('dev'));
app.use(helmet());
app.use(compression());
Auth-style middleware (sketch)
function authenticate(req, res, next) {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: 'Token required' });
}
try {
// Replace with your JWT/session verification
req.user = verifyToken(token);
next();
} catch {
res.status(401).json({ error: 'Invalid token' });
}
}
4. REST API (in-memory CRUD)
const express = require('express');
const app = express();
app.use(express.json());
let users = [
{ id: 1, name: 'Alice', email: '[email protected]' },
{ id: 2, name: 'Bob', email: '[email protected]' }
];
let nextId = 3;
app.post('/api/users', (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({ error: 'name and email required' });
}
const user = { id: nextId++, name, email };
users.push(user);
res.status(201).json(user);
});
app.get('/api/users', (req, res) => {
const page = parseInt(req.query.page, 10) || 1;
const limit = parseInt(req.query.limit, 10) || 10;
const start = (page - 1) * limit;
const slice = users.slice(start, start + limit);
res.json({
users: slice,
total: users.length,
page,
totalPages: Math.ceil(users.length / limit)
});
});
app.get('/api/users/:id', (req, res) => {
const id = parseInt(req.params.id, 10);
const user = users.find((u) => u.id === id);
if (!user) return res.status(404).json({ error: 'User not found' });
res.json(user);
});
app.put('/api/users/:id', (req, res) => {
const id = parseInt(req.params.id, 10);
const { name, email } = req.body;
const idx = users.findIndex((u) => u.id === id);
if (idx === -1) return res.status(404).json({ error: 'User not found' });
users[idx] = { id, name, email };
res.json(users[idx]);
});
app.delete('/api/users/:id', (req, res) => {
const id = parseInt(req.params.id, 10);
const idx = users.findIndex((u) => u.id === id);
if (idx === -1) return res.status(404).json({ error: 'User not found' });
users.splice(idx, 1);
res.status(204).send();
});
app.listen(3000);
HTTP status reference
| Code | Meaning | Typical use |
|---|---|---|
| 200 | OK | Successful GET/PUT |
| 201 | Created | POST created a resource |
| 204 | No Content | DELETE success |
| 400 | Bad Request | Validation failed |
| 401 | Unauthorized | Missing/invalid auth |
| 403 | Forbidden | Authenticated but not allowed |
| 404 | Not Found | Unknown route or id |
| 500 | Server Error | Unhandled exception |
5. Request and response
req.params, req.query, req.body (after parsers), req.headers, req.ip, res.send, res.json, res.status, res.redirect, res.cookie, res.render—see Express API.
6. Error handling
function asyncHandler(fn) {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
}
app.get('/users/:id', asyncHandler(async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) throw new Error('Not found');
res.json(user);
}));
app.use((err, req, res, next) => {
const status = err.statusCode || 500;
res.status(status).json({
error: {
message: err.message,
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
}
});
});
7. Examples in the Korean article
- Blog API — posts CRUD with query filters
- Multer — disk storage, file filter, size limits, error handling
- JWT + bcrypt — register/login sketch,
Bearermiddleware - EJS —
app.set('view engine','ejs'),res.render - Security — Helmet, CORS allowlist,
express-rate-limit, payload size limits - Production —
trust proxy, compression, morgan formats, PM2, Nginx reverse proxy
Code blocks match the original post; translate user-visible strings to English in your app.
8. Common issues
Headers already sent
Always return after sending an error response:
app.get('/users/:id', (req, res) => {
const user = users.find((u) => u.id === +req.params.id);
if (!user) return res.status(404).json({ error: 'Not found' });
res.json(user);
});
Middleware order
Register express.json() before routes that read req.body.
Missing next()
If a middleware does not end the response, it must call next().
Summary
Express gives you routing, middleware, and a thin layer over Node’s HTTP server—pair it with solid validation, auth, and error handling for production.
Next steps
- File system (
fs) - Database integration
- Auth and security
Resources
Related posts
- TypeScript REST API project
- Getting started with Node.js