Express.js REST API

Build CRUD APIs, test with Postman, and follow REST best practices

CRUD HTTP Methods Postman Status Codes

Table of Contents

1. Understanding REST API Basics

1.1 What is an API?

An API (Application Programming Interface) is a set of rules that allows different software applications to communicate with each other. Think of it as a waiter in a restaurant:

  • You (Client) → Place an order (Request)
  • Waiter (API) → Takes order to kitchen
  • Kitchen (Server) → Prepares food
  • Waiter (API) → Brings food back (Response)

In web development, APIs allow frontend applications (mobile apps, websites) to interact with backend servers without knowing how they're built internally.

1.2 What is REST?

REST (Representational State Transfer) is an architectural style for designing networked applications. It was introduced by Roy Fielding in 2000. REST APIs (also called RESTful APIs) follow specific principles that make them scalable, simple, and reliable.

Analogy: Think of REST as a library system. You can:

  • GET /books → See all books
  • GET /books/123 → See a specific book
  • POST /books → Add a new book
  • PUT /books/123 → Replace a book entirely
  • PATCH /books/123 → Update part of a book
  • DELETE /books/123 → Remove a book

1.3 REST Architectural Constraints

For an API to be truly RESTful, it must follow these 6 constraints:

ConstraintDescriptionExample
Client-ServerSeparation of concernsMobile app (client) communicates with server
StatelessEach request contains all necessary infoServer doesn't remember previous requests
CacheableResponses must define cacheabilityStatic data can be cached for 1 hour
Layered SystemClient can't tell if it's talking to the end serverLoad balancers, proxies in between
Uniform InterfaceStandard methods and conventionsUsing standard HTTP methods, status codes
Code-on-Demand (optional)Server can send executable codeJavaScript, Java applets

1.4 HTTP Methods and Their Meanings

HTTP methods (verbs) indicate the desired action to perform on a resource:

MethodCRUD ActionDescriptionExampleSuccess Code
GETReadRetrieve data (should never modify)GET /users/123200 OK
POSTCreateCreate a new resourcePOST /users201 Created
PUTUpdate/ReplaceReplace an entire resourcePUT /users/123200 OK
PATCHUpdate/ModifyPartially update a resourcePATCH /users/123200 OK
DELETEDeleteRemove a resourceDELETE /users/123204 No Content
HEADSame as GET but only headersHEAD /users200 OK
OPTIONSDescribe communication optionsOPTIONS /users200 OK

PUT vs PATCH:

  • PUT replaces the entire resource. Send all fields.
  • PATCH updates only specified fields. Send only what changed.
JavaScript// PUT - Replace entire user // Sending: { "name": "John", "email": "john@example.com", "age": 30 } // Result: Previous user data is completely replaced // PATCH - Update only age // Sending: { "age": 31 } // Result: Only age changes, name and email remain same

1.5 HTTP Status Codes (The Essentials)

Status codes tell the client what happened with their request. They're grouped into five classes:

2xx — Success (Everything worked)

CodeMeaningWhen to Use
200OKStandard success response for GET, PUT, PATCH
201CreatedAfter successful POST (new resource created)
204No ContentAfter successful DELETE (no response body needed)

3xx — Redirection (Further action needed)

CodeMeaningWhen to Use
301Moved PermanentlyResource URL has changed permanently
304Not ModifiedUse cached version (caching headers)

4xx — Client Error (Request problem)

CodeMeaningWhen to Use
400Bad RequestInvalid data format, missing required fields
401UnauthorizedAuthentication required or failed
403ForbiddenAuthenticated but not authorized
404Not FoundResource doesn't exist
409ConflictResource already exists (e.g., duplicate email)
422Unprocessable EntityValid format but semantic errors

5xx — Server Error (Server problem)

CodeMeaningWhen to Use
500Internal Server ErrorUnexpected server error (catch-all)
501Not ImplementedFeature not implemented
503Service UnavailableServer overloaded or maintenance

1.6 REST API Endpoint Naming Conventions

Good endpoint naming makes APIs intuitive and self-documenting:

✅ DO: Use nouns (not verbs)

ExamplesGET /users ✓ Good GET /getUsers ✗ Bad (verb in URL) POST /users ✓ Good POST /createUser ✗ Bad

✅ DO: Use plural nouns for collections

ExamplesGET /users/123 ✓ Good GET /user/123 ✗ Bad (singular)

✅ DO: Use nested resources for relationships

ExamplesGET /users/123/posts Get posts by user 123 POST /users/123/posts Create post for user 123 GET /posts/456/comments Get comments for post 456

✅ DO: Use query parameters for filtering

ExamplesGET /users?status=active Filter active users GET /users?page=2&limit=10 Pagination GET /users?sort=name&order=asc Sorting

❌ DON'T: Use file extensions

ExamplesGET /users.json ✗ Bad GET /users ✓ Good (use Accept header)

1.7 Request and Response Structure

Typical REST API Request:

HTTP RequestGET /api/users/123 HTTP/1.1 Host: example.com Authorization: Bearer token123 Accept: application/json Content-Type: application/json { "name": "John Doe" }

Typical REST API Response:

HTTP ResponseHTTP/1.1 200 OK Content-Type: application/json Cache-Control: max-age=3600 { "status": "success", "data": { "id": 123, "name": "John Doe", "email": "john@example.com" }, "message": "User retrieved successfully" }

Response Body Format Best Practice:

JavaScript// Success Response Format { "status": "success", "data": { ... }, // Single object or array "message": "Optional message", "meta": { // Optional metadata for collections "total": 100, "page": 1, "pages": 10 } } // Error Response Format { "status": "error", "error": { "code": "RESOURCE_NOT_FOUND", "message": "User with id 123 not found", "details": { ... } // Optional validation details } }

2. Building a CRUD API with Express

2.1 Project Setup and Dependencies

Bashmkdir crud-api-demo cd crud-api-demo npm init -y npm install express npm install -D nodemon # For auto-restart during development

package.json scripts:

JSON{ "scripts": { "start": "node server.js", "dev": "nodemon server.js" } }

Basic server setup (server.js):

JavaScriptconst express = require('express'); const app = express(); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use((req, res, next) => { console.log(`${req.method} ${req.path} - ${new Date().toISOString()}`); next(); }); const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Server running on http://localhost:${PORT}`); });

2.2 Understanding CRUD Operations

OperationHTTP MethodSQL EquivalentMongoose (MongoDB)
CreatePOSTINSERT.save() or .create()
ReadGETSELECT.find() or .findById()
UpdatePUT/PATCHUPDATE.findByIdAndUpdate()
DeleteDELETEDELETE.findByIdAndDelete()

2.3 CREATE (POST) – Adding New Resources

JavaScriptlet books = [ { id: 1, title: '1984', author: 'George Orwell', year: 1949 }, { id: 2, title: 'To Kill a Mockingbird', author: 'Harper Lee', year: 1960 } ]; let nextId = 3; app.post('/api/books', (req, res) => { const { title, author, year } = req.body; if (!title || !author) { return res.status(400).json({ status: 'error', message: 'Title and author are required fields' }); } const newBook = { id: nextId++, title, author, year: year || null }; books.push(newBook); res.status(201).json({ status: 'success', data: newBook, message: 'Book created successfully' }); });

2.4 READ (GET) – Retrieving Resources

JavaScriptapp.get('/api/books', (req, res) => { res.json({ status: 'success', data: books, count: books.length }); }); app.get('/api/books/:id', (req, res) => { const id = parseInt(req.params.id); const book = books.find(b => b.id === id); if (!book) { return res.status(404).json({ status: 'error', message: `Book with id ${id} not found` }); } res.json({ status: 'success', data: book }); }); app.get('/api/books/filter/by-author', (req, res) => { const { author } = req.query; if (!author) { return res.status(400).json({ status: 'error', message: 'Author query parameter is required' }); } const filteredBooks = books.filter(book => book.author.toLowerCase().includes(author.toLowerCase()) ); res.json({ status: 'success', data: filteredBooks, count: filteredBooks.length }); });

2.5 UPDATE (PUT/PATCH) – Modifying Resources

JavaScript — PUTapp.put('/api/books/:id', (req, res) => { const id = parseInt(req.params.id); const { title, author, year } = req.body; const bookIndex = books.findIndex(b => b.id === id); if (bookIndex === -1) { return res.status(404).json({ status: 'error', message: `Book with id ${id} not found` }); } if (!title || !author) { return res.status(400).json({ status: 'error', message: 'PUT requires title and author fields' }); } books[bookIndex] = { id, title, author, year: year || null }; res.json({ status: 'success', data: books[bookIndex], message: 'Book replaced successfully' }); });
JavaScript — PATCHapp.patch('/api/books/:id', (req, res) => { const id = parseInt(req.params.id); const updates = req.body; const book = books.find(b => b.id === id); if (!book) { return res.status(404).json({ status: 'error', message: `Book with id ${id} not found` }); } if (updates.title) book.title = updates.title; if (updates.author) book.author = updates.author; if (updates.year !== undefined) book.year = updates.year; res.json({ status: 'success', data: book, message: 'Book updated successfully' }); });

2.6 DELETE (DELETE) – Removing Resources

JavaScriptapp.delete('/api/books/:id', (req, res) => { const id = parseInt(req.params.id); const bookIndex = books.findIndex(b => b.id === id); if (bookIndex === -1) { return res.status(404).json({ status: 'error', message: `Book with id ${id} not found` }); } books.splice(bookIndex, 1); res.status(204).send(); }); // Soft delete let deletedBooks = []; app.delete('/api/books/:id/soft', (req, res) => { const id = parseInt(req.params.id); const bookIndex = books.findIndex(b => b.id === id); if (bookIndex === -1) { return res.status(404).json({ status: 'error', message: `Book with id ${id} not found` }); } const deletedBook = books.splice(bookIndex, 1)[0]; deletedBooks.push({ ...deletedBook, deletedAt: new Date().toISOString() }); res.json({ status: 'success', message: 'Book soft deleted successfully', data: deletedBook }); });

2.7 Complete CRUD API Example (Books API)

Here's a complete, production-ready CRUD API with all endpoints:

JavaScript — Complete Books APIconst express = require('express'); const app = express(); app.use(express.json()); let books = [ { id: 1, title: '1984', author: 'George Orwell', year: 1949, isbn: '978-0451524935' }, { id: 2, title: 'To Kill a Mockingbird', author: 'Harper Lee', year: 1960, isbn: '978-0061120084' } ]; let nextId = 3; const findBookIndex = (id) => books.findIndex(book => book.id === id); app.post('/api/books', (req, res) => { const { title, author, year, isbn } = req.body; const errors = []; if (!title) errors.push('Title is required'); if (!author) errors.push('Author is required'); if (year && (typeof year !== 'number' || year < 0 || year > new Date().getFullYear())) { errors.push('Year must be a valid year'); } if (errors.length > 0) { return res.status(400).json({ status: 'error', errors }); } const newBook = { id: nextId++, title, author, year: year || null, isbn: isbn || null, createdAt: new Date().toISOString() }; books.push(newBook); res.status(201).json({ status: 'success', data: newBook }); }); app.get('/api/books', (req, res) => { let result = [...books]; if (req.query.author) { result = result.filter(book => book.author.toLowerCase().includes(req.query.author.toLowerCase()) ); } if (req.query.minYear) { result = result.filter(book => book.year >= parseInt(req.query.minYear)); } const page = parseInt(req.query.page) || 1; const limit = parseInt(req.query.limit) || 10; const startIndex = (page - 1) * limit; const paginatedResult = result.slice(startIndex, page * limit); res.json({ status: 'success', data: paginatedResult, pagination: { total: result.length, page, limit, pages: Math.ceil(result.length / limit) } }); }); app.get('/api/books/:id', (req, res) => { const id = parseInt(req.params.id); const book = books.find(b => b.id === id); if (!book) { return res.status(404).json({ status: 'error', message: `Book with ID ${id} not found` }); } res.json({ status: 'success', data: book }); }); app.put('/api/books/:id', (req, res) => { const id = parseInt(req.params.id); const { title, author, year, isbn } = req.body; const index = findBookIndex(id); if (index === -1) { return res.status(404).json({ status: 'error', message: `Book with ID ${id} not found` }); } if (!title || !author) { return res.status(400).json({ status: 'error', message: 'Title and author are required' }); } books[index] = { id, title, author, year: year || books[index].year, isbn: isbn || books[index].isbn, updatedAt: new Date().toISOString() }; res.json({ status: 'success', data: books[index] }); }); app.patch('/api/books/:id', (req, res) => { const id = parseInt(req.params.id); const updates = req.body; const book = books.find(b => b.id === id); if (!book) { return res.status(404).json({ status: 'error', message: `Book with ID ${id} not found` }); } const allowedUpdates = ['title', 'author', 'year', 'isbn']; const requestedUpdates = Object.keys(updates); const isValidOperation = requestedUpdates.every(u => allowedUpdates.includes(u)); if (!isValidOperation) { return res.status(400).json({ status: 'error', message: 'Invalid update fields' }); } requestedUpdates.forEach(update => { if (updates[update] !== undefined) book[update] = updates[update]; }); book.updatedAt = new Date().toISOString(); res.json({ status: 'success', data: book }); }); app.delete('/api/books/:id', (req, res) => { const id = parseInt(req.params.id); const index = findBookIndex(id); if (index === -1) { return res.status(404).json({ status: 'error', message: `Book with ID ${id} not found` }); } books.splice(index, 1); res.status(204).send(); }); app.use((err, req, res, next) => { console.error(err.stack); res.status(500).json({ status: 'error', message: 'Something went wrong on the server' }); }); app.use((req, res) => { res.status(404).json({ status: 'error', message: `Cannot ${req.method} ${req.url}` }); }); app.listen(3000, () => console.log('Books API running on http://localhost:3000'));

3. Testing APIs with Postman

3.1 What is Postman?

Postman is a popular API development and testing tool that allows you to:

  • Send HTTP requests (GET, POST, PUT, DELETE, etc.)
  • View responses with formatting
  • Organize requests into collections
  • Automate testing with scripts
  • Share APIs with team members
  • Generate documentation

3.2 Installation and Setup

Installation:

  • Download from postman.com/downloads
  • Install for your operating system
  • Create a free account (optional, but recommended for cloud sync)

Postman Interface Overview:

  • Sidebar: Collections, History, APIs
  • Request Builder: URL bar, method selector, tabs (Params, Authorization, Headers, Body)
  • Response Area: Status code, time, size, body preview
  • Console: View logs and debug information

3.3 Making GET Requests

  1. Select GET method from dropdown
  2. Enter URL: http://localhost:3000/api/books
  3. Click Send
Expected Response[GET] ▼ http://localhost:3000/api/books [Send] Status: 200 OK Body: { "status": "success", "data": [ { "id": 1, "title": "1984", "author": "George Orwell", "year": 1949 }, { "id": 2, "title": "To Kill a Mockingbird", "author": "Harper Lee", "year": 1960 } ], "count": 2 }

GET with query parameters: URL: http://localhost:3000/api/books?author=Orwell — click Params tab and add Key: author, Value: Orwell.

3.4 Making POST Requests (Sending Data)

  1. Select POST method
  2. URL: http://localhost:3000/api/books
  3. Select Body tab → choose raw and JSON
  4. Enter JSON data and click Send
JSON Request Body{ "title": "The Great Gatsby", "author": "F. Scott Fitzgerald", "year": 1925, "isbn": "978-0743273565" }
Expected Response (201 Created){ "status": "success", "data": { "id": 3, "title": "The Great Gatsby", "author": "F. Scott Fitzgerald", "year": 1925, "isbn": "978-0743273565", "createdAt": "2024-01-15T10:30:00.000Z" } }

Testing validation errors:

JSON — Missing Fields (400 Bad Request)// Request: { "title": "Incomplete Book" } // Response: { "status": "error", "errors": ["Author is required"] }

3.5 Making PUT/PATCH Requests

PUT Request (Full Update): Method PUT, URL http://localhost:3000/api/books/3

JSON{ "title": "The Great Gatsby - Updated", "author": "F. Scott Fitzgerald", "year": 1925, "isbn": "978-0743273565" }

PATCH Request (Partial Update): Method PATCH, URL http://localhost:3000/api/books/3

JSON{ "title": "The Great Gatsby (Revised Edition)" }

3.6 Making DELETE Requests

  • Method: DELETE
  • URL: http://localhost:3000/api/books/3
  • Expected: Status 204 No Content, empty body

3.7 Working with Environments and Variables

Instead of typing http://localhost:3000 every time, use {{baseUrl}}.

  1. Click Environments → create Development
  2. Add variable: baseUrl = http://localhost:3000
  3. Use in URL: {{baseUrl}}/api/books
Postman Tests Tab// Save the ID of newly created resource const response = pm.response.json(); pm.environment.set("bookId", response.data.id); // Then use: {{baseUrl}}/api/books/{{bookId}}

3.8 Creating Collections for Organized Testing

Collection Structure📁 Books API Tests ├── 📁 Books │ ├── 📄 GET All Books │ ├── 📄 GET Single Book │ ├── 📄 POST Create Book │ ├── 📄 PUT Update Book │ ├── 📄 PATCH Partial Update │ └── 📄 DELETE Remove Book └── 📁 Validation Tests ├── 📄 POST Missing Fields └── 📄 GET Non-existent Book

3.9 Writing Basic Tests in Postman

JavaScript — Test Script Structurepm.test("Status code is 200", function () { pm.response.to.have.status(200); }); pm.test("Response has correct structure", function () { const jsonData = pm.response.json(); pm.expect(jsonData).to.have.property("status"); pm.expect(jsonData.status).to.equal("success"); });
GET All Books Testspm.test("Status code is 200", () => pm.response.to.have.status(200)); pm.test("Response is JSON", () => pm.response.to.be.json); pm.test("Data is an array", () => { const response = pm.response.json(); pm.expect(response.data).to.be.an("array"); }); pm.test("Book '1984' exists", () => { const books = pm.response.json().data; const book1984 = books.find(b => b.title === "1984"); pm.expect(book1984).to.exist; pm.expect(book1984.author).to.equal("George Orwell"); });
POST Create Book Testspm.test("Status code is 201 Created", () => pm.response.to.have.status(201)); pm.test("Save book ID to environment", () => { const response = pm.response.json(); pm.environment.set("lastCreatedId", response.data.id); });
DELETE Book Testspm.test("Status code is 204 No Content", () => { pm.response.to.have.status(204); });

4. Advanced REST API Concepts

4.1 Request Query Parameters (Filtering, Sorting, Pagination)

JavaScriptapp.get('/api/books', (req, res) => { let query = [...books]; if (req.query.author) { query = query.filter(book => book.author === req.query.author); } if (req.query.search) { const searchTerm = req.query.search.toLowerCase(); query = query.filter(book => book.title.toLowerCase().includes(searchTerm) || book.author.toLowerCase().includes(searchTerm) ); } if (req.query.minYear) { query = query.filter(book => book.year >= parseInt(req.query.minYear)); } if (req.query.maxYear) { query = query.filter(book => book.year <= parseInt(req.query.maxYear)); } if (req.query.sort) { const sortField = req.query.sort; const sortOrder = req.query.order === 'desc' ? -1 : 1; query.sort((a, b) => { if (a[sortField] < b[sortField]) return -1 * sortOrder; if (a[sortField] > b[sortField]) return 1 * sortOrder; return 0; }); } const page = parseInt(req.query.page) || 1; const limit = parseInt(req.query.limit) || 10; const startIndex = (page - 1) * limit; const endIndex = page * limit; const total = query.length; const paginatedQuery = query.slice(startIndex, endIndex); res.json({ status: 'success', count: paginatedQuery.length, data: paginatedQuery, pagination: { total, page, limit, pages: Math.ceil(total / limit), next: endIndex < total ? page + 1 : null, prev: startIndex > 0 ? page - 1 : null } }); }); // GET /api/books?author=Orwell&minYear=1940&maxYear=1960&sort=year&order=asc&page=1&limit=5

4.2 Request Headers (Authentication, Content-Type)

JavaScriptconst authenticate = (req, res, next) => { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ status: 'error', message: 'No token provided or invalid format' }); } const token = authHeader.split(' ')[1]; if (token !== 'secret-token-123') { return res.status(401).json({ status: 'error', message: 'Invalid authentication token' }); } req.user = { id: 1, name: 'Admin User', role: 'admin' }; next(); }; app.post('/api/admin/books', authenticate, (req, res) => { res.json({ status: 'success', message: `Book created by ${req.user.name}`, user: req.user }); }); app.get('/api/secure-data', (req, res) => { res.set({ 'X-Content-Type-Options': 'nosniff', 'X-Frame-Options': 'DENY', 'Cache-Control': 'no-store, no-cache, must-revalidate, private' }); res.json({ secret: 'This is protected data' }); });

4.3 Error Handling in REST APIs

JavaScriptclass AppError extends Error { constructor(message, statusCode) { super(message); this.statusCode = statusCode; this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error'; this.isOperational = true; Error.captureStackTrace(this, this.constructor); } } class ValidationError extends AppError { constructor(message, details) { super(message, 400); this.details = details; this.name = 'ValidationError'; } } class NotFoundError extends AppError { constructor(resource, id) { super(`${resource} with id ${id} not found`, 404); } } const catchAsync = (fn) => (req, res, next) => fn(req, res, next).catch(next); app.get('/api/books/:id', catchAsync(async (req, res) => { const id = parseInt(req.params.id); const book = books.find(b => b.id === id); if (!book) throw new NotFoundError('Book', id); res.json({ status: 'success', data: book }); })); app.use((err, req, res, next) => { err.statusCode = err.statusCode || 500; err.status = err.status || 'error'; console.error('Error:', { message: err.message, url: req.url, method: req.method }); if (err.isOperational) { res.status(err.statusCode).json({ status: err.status, message: err.message, ...(err.details && { details: err.details }) }); } else { res.status(500).json({ status: 'error', message: 'Something went wrong!' }); } });

4.4 API Versioning Strategies

Strategy 1: URL Path Versioning (Most Common)

JavaScriptapp.get('/api/v1/books', (req, res) => { res.json(books.map(b => ({ id: b.id, title: b.title }))); }); app.get('/api/v2/books', (req, res) => { res.json(books); }); app.get('/api/v3/books', (req, res) => { res.json(books.map(b => ({ ...b, _links: { self: `/api/v3/books/${b.id}`, author: `/api/v3/authors/${b.authorId}` } }))); });

Strategy 2: Custom Header Versioning

JavaScriptapp.get('/api/books', (req, res) => { const version = req.headers['api-version'] || '1.0'; switch(version) { case '1.0': return res.json(books.map(b => ({ id: b.id, title: b.title }))); case '2.0': return res.json(books); default: return res.status(400).json({ status: 'error', message: 'Unsupported API version' }); } }); // Client: GET /api/books Headers: { "api-version": "2.0" }

Strategy 3: Accept Header Versioning

JavaScriptapp.get('/api/books', (req, res) => { const acceptHeader = req.headers.accept || ''; if (acceptHeader.includes('application/vnd.myapi.v2+json')) { return res.json(books); } else if (acceptHeader.includes('application/vnd.myapi.v1+json')) { return res.json(books.map(b => ({ id: b.id, title: b.title }))); } return res.json(books); }); // Client: Headers: { "Accept": "application/vnd.myapi.v2+json" }

5. Complete Project: Library Management API

The Books API in section 2.7 serves as a complete Library Management API. It implements all CRUD endpoints with validation, pagination, filtering, and error handling.

API Endpoints:

MethodEndpointDescription
GET/api/booksList all books (with filtering & pagination)
GET/api/books/:idGet a single book by ID
POST/api/booksAdd a new book to the library
PUT/api/books/:idReplace a book entirely
PATCH/api/books/:idPartially update a book
DELETE/api/books/:idRemove a book from the library

Test the full workflow in Postman using the collection structure from section 3.8. Start the server with npm run dev, then run through create → read → update → delete operations.

6. Best Practices for REST API Development

6.1 Naming and Structure

PracticeBad ExampleGood Example
Use nouns, not verbs/getBooks/books
Use plural for collections/book/books
Use nested for relationships/getBookComments?bookId=123/books/123/comments
Consistent case/UserProfile/user-profile

6.2 HTTP Methods and Status Codes

Guidelines// Always use appropriate methods ✓ GET for retrieval ✓ POST for creation ✓ PUT for full updates ✓ PATCH for partial updates ✓ DELETE for deletion // Return correct status codes 201 Created → after POST (with location header) 200 OK → after successful GET, PUT, PATCH 204 No Content → after DELETE 400 Bad Request → validation errors 401 Unauthorized → missing/invalid auth 403 Forbidden → authenticated but not allowed 404 Not Found → resource doesn't exist 422 Unprocessable Entity → semantic errors 500 Internal Server Error → unexpected errors

6.3 Security Best Practices

JavaScript// 1. Validate all inputs with Joi const Joi = require('joi'); const bookSchema = Joi.object({ title: Joi.string().required().max(100), author: Joi.string().required(), year: Joi.number().integer().min(1000).max(new Date().getFullYear()), isbn: Joi.string().pattern(/^[0-9-]{10,17}$/) }); // 2. Sanitize output to prevent XSS const escapeHtml = require('escape-html'); // 3. Set security headers const helmet = require('helmet'); app.use(helmet()); // 4. Rate limiting const rateLimit = require('express-rate-limit'); app.use('/api/', rateLimit({ windowMs: 15 * 60 * 1000, max: 100 })); // 5. Use HTTPS in production if (process.env.NODE_ENV === 'production') { app.use((req, res, next) => { if (req.header('x-forwarded-proto') !== 'https') { res.redirect(`https://${req.header('host')}${req.url}`); } else { next(); } }); }

6.4 Documentation

JavaScript — Swagger/OpenAPIconst swaggerUi = require('swagger-ui-express'); const swaggerDocument = { openapi: '3.0.0', info: { title: 'Library API', version: '1.0.0', description: 'A simple library management API' }, servers: [{ url: 'http://localhost:3000', description: 'Development server' }], paths: { '/api/books': { get: { summary: 'Returns all books', parameters: [ { name: 'author', in: 'query', schema: { type: 'string' } }, { name: 'page', in: 'query', schema: { type: 'integer' } } ], responses: { 200: { description: 'Successful response' } } } } } }; app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));

7. Conclusion

You've now learned the fundamentals of building REST APIs with Express.js:

REST API Basics

  • REST uses standard HTTP methods (GET, POST, PUT, PATCH, DELETE)
  • Status codes communicate results (200 success, 404 not found, 500 error)
  • Endpoints should use nouns, plural forms, and logical nesting

CRUD Operations

  • CREATE (POST) — Add new resources
  • READ (GET) — Retrieve resources (single or collection)
  • UPDATE (PUT/PATCH) — Modify existing resources
  • DELETE (DELETE) — Remove resources

Express Implementation

  • Use express.json() middleware for parsing JSON
  • Implement proper validation and error handling
  • Structure routes logically (separate files for larger apps)

Postman Testing

  • Create collections to organize requests
  • Use environments for different configurations
  • Write tests to automate validation
  • Save variables from responses for chained requests

Best Practices: Validate all inputs, use appropriate HTTP status codes, implement rate limiting and security headers, document your API, and version your API from the start.