From Theory to a Working In-Memory Application
The Blueprint for Modern Web Services
REpresentational State Transfer is an architectural style for designing networked applications. It's not a protocol or a standard; it's a set of constraints.
The goal is to create APIs that are simple, scalable, and predictable.
We model our data as resources (e.g., users, products, posts). We use standard HTTP verbs to perform actions on these resources.
| Operation | CRUD | HTTP Verb | Example Route | Description |
|---|---|---|---|---|
| Create | Create | POST | `/users` | Create a new user. |
| Read | Read | GET | `/users`, `/users/:id` | Retrieve a list of users or a single user. |
| Update | Update | PUT / PATCH | `/users/:id` | Update a user's information. |
| Delete | Delete | DELETE | `/users/:id` | Delete a user. |
Good URLs are predictable and easy to understand.
Good: `GET /users/42/comments`
Bad: `POST /getUserComments?userId=42`
Status codes are a standard way for the server to tell the client the outcome of its request.
From `npm init` to a Working Server
We will use Separation of Concerns to keep our code organized.
/my-api
├── node_modules/
├── data/
│ └── users.js (Our in-memory database)
├── controllers/
│ └── userController.js (The logic for each route)
├── routes/
│ └── userRoutes.js (The Express Router)
├── app.js (The main server file)
└── package.json
To isolate our API logic from database complexity, we'll start with a simple array.
`data/users.js`
let users = [
{ id: 1, name: 'Alex Architect', email: 'alex@example.com' },
{ id: 2, name: 'Jane Developer', email: 'jane@example.com' }
];
module.exports = users;
Controllers contain the actual logic for handling a request.
`controllers/userController.js`
let users = require('../data/users');
let nextId = 3;
// GET /users
const getAllUsers = (req, res) => {
res.json(users);
};
// GET /users/:id
const getUserById = (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) {
return res.status(404).send('User not found');
}
res.json(user);
};
// ... other functions for create, update, delete ...
module.exports = { getAllUsers, getUserById /*, ... */ };
The router connects an HTTP verb and a URL path to a controller function.
`routes/userRoutes.js`
const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');
router.get('/', userController.getAllUsers);
router.get('/:id', userController.getUserById);
// router.post('/', userController.createUser);
// router.patch('/:id', userController.updateUser);
// router.delete('/:id', userController.deleteUser);
module.exports = router;
`app.js`
const express = require('express');
const app = express();
const userRoutes = require('./routes/userRoutes');
const PORT = 3000;
// Middleware to parse JSON bodies
app.use(express.json());
// Mount the router
app.use('/users', userRoutes);
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
Bringing the API to Life
Read the new user data from `req.body`, add it to the array, and return the new user with a `201 Created` status.
// In userController.js
const createUser = (req, res) => {
if (!req.body.name || !req.body.email) {
return res.status(400).send('Name and email are required.');
}
const newUser = {
id: nextId++,
name: req.body.name,
email: req.body.email
};
users.push(newUser);
res.status(201).json(newUser);
};
Find the user by their ID from `req.params`, update their data from `req.body`, and return the updated user.
// In userController.js
const updateUser = (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) return res.status(404).send('User not found');
// Update properties if they exist in the request body
if (req.body.name) user.name = req.body.name;
if (req.body.email) user.email = req.body.email;
res.json(user);
};
Find the user by their ID from `req.params`, remove them from the array, and return a `204 No Content` status.
// In userController.js
const deleteUser = (req, res) => {
const userIndex = users.findIndex(u => u.id === parseInt(req.params.id));
if (userIndex === -1) return res.status(404).send('User not found');
users.splice(userIndex, 1);
res.status(204).send();
};
You will be given a project with a working `users` API. Your task is to architect and implement a new, complete CRUD API for a `posts` resource from scratch.