Table of Contents
- Prerequisites
- Setting Up the Project
- Understanding RESTful Principles
- Building Basic API Endpoints
- Integrating a Database
- Adding Middleware
- Testing the API
- Deployment
- Best Practices for RESTful APIs
- Conclusion
- References
Prerequisites
Before diving in, ensure you have the following:
- Basic JavaScript Knowledge: Familiarity with ES6+ features (arrow functions, async/await, modules).
- Node.js and npm: Installed on your machine. Download from nodejs.org.
- Code Editor: VS Code (recommended) or any editor of your choice.
- Postman or curl: For testing API endpoints.
- MongoDB Account (Optional): For database integration (we’ll use MongoDB Atlas, a cloud-hosted service).
Setting Up the Project
Let’s start by creating a new Node.js project and installing dependencies.
Step 1: Initialize the Project
Open your terminal and run:
mkdir js-rest-api && cd js-rest-api
npm init -y
This creates a package.json file to manage dependencies.
Step 2: Install Dependencies
We’ll use Express (a minimal web framework) and nodemon (to auto-reload the server during development):
npm install express
npm install --save-dev nodemon
Step 3: Create the Server File
Create a server.js file in the project root:
// server.js
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
// Basic route
app.get('/', (req, res) => {
res.send('Hello, RESTful API!');
});
// Start server
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Step 4: Configure Auto-Reload
Update package.json to add a dev script for nodemon:
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
}
Step 5: Test the Server
Run the server with:
npm run dev
Visit http://localhost:3000 in your browser. You should see “Hello, RESTful API!“.
Understanding RESTful Principles
REST (Representational State Transfer) is an architectural style for designing networked applications. Key principles include:
1. Resources
APIs expose resources (e.g., users, books, products) identified by URIs (Uniform Resource Identifiers).
Example: GET /api/books (retrieves all books).
2. HTTP Methods
Use standard HTTP methods to interact with resources:
GET: Retrieve a resource(s).POST: Create a new resource.PUT: Update a resource (replace entire resource).PATCH: Partially update a resource.DELETE: Remove a resource.
3. Statelessness
The server doesn’t store client state. Each request must contain all information needed to process it.
4. HTTP Status Codes
Use standard status codes to indicate request success/failure:
200 OK: Success.201 Created: Resource created.400 Bad Request: Invalid input.404 Not Found: Resource not found.500 Internal Server Error: Server-side error.
5. Uniform Interface
Resources are represented in formats like JSON or XML (we’ll use JSON).
Building Basic API Endpoints
Let’s build a “Book API” with CRUD operations. We’ll start with an in-memory data store, then integrate a database later.
In-Memory Data Store
Add a books array to server.js to simulate a database:
let books = [
{ id: 1, title: "The Great Gatsby", author: "F. Scott Fitzgerald" },
{ id: 2, title: "1984", author: "George Orwell" }
];
Implementing CRUD Operations
We’ll use Express routes to handle HTTP methods. First, enable JSON parsing (required to read request bodies) by adding middleware:
// Add this near the top of server.js
app.use(express.json()); // Parses JSON request bodies
1. GET All Books (GET /api/books)
app.get('/api/books', (req, res) => {
res.status(200).json(books);
});
2. GET a Single Book (GET /api/books/:id)
Use req.params.id to fetch a book by ID:
app.get('/api/books/:id', (req, res) => {
const book = books.find(b => b.id === parseInt(req.params.id));
if (!book) return res.status(404).json({ message: "Book not found" });
res.status(200).json(book);
});
3. POST (Create) a Book (POST /api/books)
Validate input and add a new book to the array:
app.post('/api/books', (req, res) => {
if (!req.body.title || !req.body.author) {
return res.status(400).json({ message: "Title and author are required" });
}
const newBook = {
id: books.length + 1,
title: req.body.title,
author: req.body.author
};
books.push(newBook);
res.status(201).json(newBook); // 201 = Created
});
4. PUT (Update) a Book (PUT /api/books/:id)
Replace an existing book with new data:
app.put('/api/books/:id', (req, res) => {
const bookIndex = books.findIndex(b => b.id === parseInt(req.params.id));
if (bookIndex === -1) return res.status(404).json({ message: "Book not found" });
if (!req.body.title || !req.body.author) {
return res.status(400).json({ message: "Title and author are required" });
}
books[bookIndex] = {
...books[bookIndex],
title: req.body.title,
author: req.body.author
};
res.status(200).json(books[bookIndex]);
});
5. DELETE a Book (DELETE /api/books/:id)
Remove a book from the array:
app.delete('/api/books/:id', (req, res) => {
const bookIndex = books.findIndex(b => b.id === parseInt(req.params.id));
if (bookIndex === -1) return res.status(404).json({ message: "Book not found" });
books.splice(bookIndex, 1);
res.status(200).json({ message: "Book deleted" });
});
Integrating a Database
In-memory data resets when the server restarts. Let’s use MongoDB (a NoSQL database) for persistent storage.
MongoDB and Mongoose Setup
Step 1: Sign Up for MongoDB Atlas
- Go to MongoDB Atlas and create a free account.
- Create a cluster, then a database user, and whitelist your IP (or allow all IPs for development).
- Get your MongoDB connection string (e.g.,
mongodb+srv://<user>:<password>@cluster0.mongodb.net/booksDB).
Step 2: Install Dependencies
npm install mongoose dotenv
mongoose: ODM (Object Data Modeling) library for MongoDB.dotenv: Loads environment variables from a.envfile.
Step 3: Configure Environment Variables
Create a .env file in the project root:
MONGODB_URI=your_mongodb_connection_string
PORT=3000
Step 4: Connect to MongoDB
Update server.js to connect to MongoDB using Mongoose:
require('dotenv').config();
const mongoose = require('mongoose');
// Connect to MongoDB
mongoose.connect(process.env.MONGODB_URI)
.then(() => console.log('Connected to MongoDB'))
.catch(err => console.error('MongoDB connection error:', err));
Step 5: Create a Mongoose Schema and Model
Define a Book schema to enforce data structure:
const bookSchema = new mongoose.Schema({
title: { type: String, required: true },
author: { type: String, required: true }
});
const Book = mongoose.model('Book', bookSchema);
Updating Endpoints to Use MongoDB
Replace the in-memory array with Mongoose methods (async/await):
GET All Books
app.get('/api/books', async (req, res) => {
try {
const books = await Book.find();
res.status(200).json(books);
} catch (err) {
res.status(500).json({ message: err.message });
}
});
GET a Single Book
app.get('/api/books/:id', async (req, res) => {
try {
const book = await Book.findById(req.params.id);
if (!book) return res.status(404).json({ message: "Book not found" });
res.status(200).json(book);
} catch (err) {
res.status(500).json({ message: err.message });
}
});
POST a Book
app.post('/api/books', async (req, res) => {
const book = new Book({
title: req.body.title,
author: req.body.author
});
try {
const newBook = await book.save();
res.status(201).json(newBook);
} catch (err) {
res.status(400).json({ message: err.message }); // Validation error
}
});
PUT a Book
app.put('/api/books/:id', async (req, res) => {
try {
const book = await Book.findById(req.params.id);
if (!book) return res.status(404).json({ message: "Book not found" });
book.title = req.body.title || book.title;
book.author = req.body.author || book.author;
const updatedBook = await book.save();
res.status(200).json(updatedBook);
} catch (err) {
res.status(400).json({ message: err.message });
}
});
DELETE a Book
app.delete('/api/books/:id', async (req, res) => {
try {
const book = await Book.findById(req.params.id);
if (!book) return res.status(404).json({ message: "Book not found" });
await book.deleteOne();
res.status(200).json({ message: "Book deleted" });
} catch (err) {
res.status(500).json({ message: err.message });
}
});
Adding Middleware
Middleware are functions that run between the request and response. Let’s add useful middleware.
Logging Middleware
Log request details (method, URL, timestamp):
const logger = (req, res, next) => {
console.log(`${req.method} ${req.originalUrl} - ${new Date().toISOString()}`);
next(); // Call the next middleware/route handler
};
app.use(logger); // Apply to all routes
Error Handling Middleware
Centralize error handling with a custom middleware:
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ message: "Something went wrong!" });
});
CORS Middleware
Allow cross-origin requests (e.g., from a React frontend) with cors:
npm install cors
const cors = require('cors');
app.use(cors()); // Enable CORS for all routes
Testing the API
Use Postman to test endpoints:
- GET /api/books: Retrieve all books.
- POST /api/books: Send a JSON body
{ "title": "To Kill a Mockingbird", "author": "Harper Lee" }to create a book. - GET /api/books/:id: Replace
:idwith the ID of a book to fetch it. - PUT /api/books/:id: Update a book’s title/author.
- DELETE /api/books/:id: Delete a book.
Deployment
Deploy your API to a cloud platform like Heroku:
Step 1: Prepare for Deployment
- Add a
Procfile(no extension) to the project root:web: node server.js - Ensure
package.jsonhas"engines"to specify Node.js version:"engines": { "node": "18.x" }
Step 2: Deploy to Heroku
- Install Heroku CLI: devcenter.heroku.com/articles/heroku-cli.
- Login and create an app:
heroku login heroku create my-js-rest-api - Set environment variables:
heroku config:set MONGODB_URI=your_mongodb_connection_string - Push code to Heroku:
git push heroku main - Open the app:
heroku open
Best Practices for RESTful APIs
- Validation: Use
express-validatorto validate request data. - Versioning: Include version in the URI (e.g.,
/api/v1/books). - Documentation: Use Swagger/OpenAPI to document endpoints.
- Security:
- Use HTTPS.
- Sanitize inputs to prevent injection attacks.
- Implement rate limiting with
express-rate-limit.
- Error Handling: Send consistent error responses (e.g.,
{ "error": "Message" }).
Conclusion
You’ve built a fully functional RESTful API with JavaScript, Express, and MongoDB! You learned to:
- Set up a Node.js project with Express.
- Implement CRUD operations.
- Integrate MongoDB for persistent storage.
- Add middleware for logging, CORS, and error handling.
- Test and deploy the API.
Continue exploring by adding authentication (JWT), pagination, or file uploads!