Building Your First REST API
Building Your First REST API
REST stands for Representational State Transfer. It is an architectural style for designing networked APIs. REST APIs use standard HTTP methods (GET, POST, PUT, DELETE) and URLs to perform operations on resources. Almost every public API — from Twitter to Paystack to GitHub — is a REST API.
In this lesson, you will build a complete REST API for managing a list of products, applying everything you have learned so far.
REST Conventions
| HTTP Method | URL | Action |
|---|---|---|
| GET | /products | Return all products |
| GET | /products/:id | Return a single product |
| POST | /products | Create a new product |
| PUT | /products/:id | Replace an entire product |
| PATCH | /products/:id | Update specific fields of a product |
| DELETE | /products/:id | Delete a product |
Project Structure
my-api/
index.js
routes/
products.js
controllers/
productController.js
data/
products.js
package.json
Step 1: In-Memory Data Store
Before connecting to a real database, use an in-memory array to store data:
// data/products.js
let products = [
{ id: 1, name: 'Wireless Mouse', price: 4500, category: 'Electronics', stock: 50 },
{ id: 2, name: 'Standing Desk', price: 85000, category: 'Furniture', stock: 10 },
{ id: 3, name: 'USB-C Hub', price: 12000, category: 'Electronics', stock: 75 },
];
let nextId = 4;
function getAll() { return products; }
function getById(id) { return products.find(p => p.id === parseInt(id)); }
function create(data) {
const product = { id: nextId++, ...data };
products.push(product);
return product;
}
function update(id, data) {
const index = products.findIndex(p => p.id === parseInt(id));
if (index === -1) return null;
products[index] = { ...products[index], ...data };
return products[index];
}
function remove(id) {
const index = products.findIndex(p => p.id === parseInt(id));
if (index === -1) return false;
products.splice(index, 1);
return true;
}
module.exports = { getAll, getById, create, update, remove };
Step 2: Controller
// controllers/productController.js
const store = require('../data/products');
exports.getAllProducts = (req, res) => {
const { category, minPrice, maxPrice } = req.query;
let products = store.getAll();
if (category) {
products = products.filter(p => p.category.toLowerCase() === category.toLowerCase());
}
if (minPrice) {
products = products.filter(p => p.price >= parseFloat(minPrice));
}
if (maxPrice) {
products = products.filter(p => p.price <= parseFloat(maxPrice));
}
res.json({ success: true, count: products.length, data: products });
};
exports.getProductById = (req, res) => {
const product = store.getById(req.params.id);
if (!product) {
return res.status(404).json({ success: false, error: 'Product not found' });
}
res.json({ success: true, data: product });
};
exports.createProduct = (req, res) => {
const { name, price, category, stock } = req.body;
if (!name || !price || !category) {
return res.status(400).json({ success: false, error: 'Name, price, and category are required' });
}
const product = store.create({ name, price: parseFloat(price), category, stock: stock || 0 });
res.status(201).json({ success: true, data: product });
};
exports.updateProduct = (req, res) => {
const product = store.update(req.params.id, req.body);
if (!product) {
return res.status(404).json({ success: false, error: 'Product not found' });
}
res.json({ success: true, data: product });
};
exports.deleteProduct = (req, res) => {
const deleted = store.remove(req.params.id);
if (!deleted) {
return res.status(404).json({ success: false, error: 'Product not found' });
}
res.sendStatus(204);
};
Step 3: Router
// routes/products.js
const express = require('express');
const router = express.Router();
const ctrl = require('../controllers/productController');
router.get('/', ctrl.getAllProducts);
router.get('/:id', ctrl.getProductById);
router.post('/', ctrl.createProduct);
router.put('/:id', ctrl.updateProduct);
router.patch('/:id', ctrl.updateProduct);
router.delete('/:id', ctrl.deleteProduct);
module.exports = router;
Step 4: Main Server
// index.js
const express = require('express');
const cors = require('cors');
const morgan = require('morgan');
const productRoutes = require('./routes/products');
const app = express();
app.use(cors());
app.use(morgan('dev'));
app.use(express.json());
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'ok', uptime: process.uptime() });
});
// Mount routes
app.use('/api/products', productRoutes);
// 404 handler
app.use((req, res) => {
res.status(404).json({ success: false, error: 'Route not found' });
});
// Error handler
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ success: false, error: 'Internal server error' });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`API running at http://localhost:${PORT}`);
});
Testing Your API
# Get all products
curl http://localhost:3000/api/products
# Get products filtered by category
curl "http://localhost:3000/api/products?category=Electronics"
# Get a single product
curl http://localhost:3000/api/products/1
# Create a product
curl -X POST http://localhost:3000/api/products \
-H "Content-Type: application/json" \
-d '{"name":"Laptop Stand","price":8000,"category":"Accessories","stock":30}'
# Update a product
curl -X PATCH http://localhost:3000/api/products/1 \
-H "Content-Type: application/json" \
-d '{"price":5000}'
# Delete a product
curl -X DELETE http://localhost:3000/api/products/1
Key Takeaways
- REST uses standard HTTP methods (GET, POST, PUT, PATCH, DELETE) to perform operations on resources.
- Separate concerns: routes define the URLs, controllers contain the logic, data modules handle storage.
- Validate all input, return appropriate HTTP status codes, and always return a consistent JSON structure.
- Filter and query functionality via
req.querymakes APIs flexible without creating extra routes. - Test your API with curl, Postman, or any HTTP client before writing the frontend.
Practice Exercise
- Add a
GET /api/products/statsroute that returns the total number of products, average price, and a breakdown by category. - Add pagination to
GET /api/productsusing?page=1&limit=10query parameters. - Add a
GET /api/products/search?q=mouseroute that returns products whose name or category matches the search term (case-insensitive). - Try to create a product with missing fields and verify you get a 400 error.
Try it yourself
Key Takeaways
- REST APIs use HTTP methods (GET, POST, PUT, PATCH, DELETE) on URLs to perform CRUD operations on resources.
- Separate routes, controllers, and data layers — each file has one clear responsibility.
- GET retrieves, POST creates, PUT replaces, PATCH partially updates, and DELETE removes a resource.
- Validate all input in controllers and return appropriate status codes (400, 404, 201, 204).
- Test every endpoint with curl or Postman before building a frontend that depends on it.
Quick Quiz
1.What does REST stand for?
2.Which HTTP method should you use to update ONLY specific fields of a resource?
3.In an MVC-style Express project, what is the purpose of a controller?
4.What status code and method should a successful DELETE endpoint return when it has no body to send?
Ready to go further?
CareerEx gives you structured 12-week training, live classes every Saturday and Sunday, real tutor feedback, and a certificate. Join the next cohort.
Join CareerEx