Build RESTful APIs using Express, the most widely used framework in the Node ecosystem.
Welcome to setting up Express projects! In this chapter, you'll learn how to create and organize Express applications effectively. We'll cover the essentials of project structure, best practices, and tools that will help you build robust APIs.
Let's start by creating a new project using the Node.js command line. You can use either npm or yarn to initialize your project.
mkdir my-express-app
cd my-express-app
npm init -y
Install Express and required dependencies:
npm install express dotenv --save
npm install nodemon --save-dev
Boilerplates can save you time when starting a new project. Look for popular Express boilerplates on GitHub or npm.
// Example: Using an Express boilerplate
git clone https://github.com/example/express-boilerplate.git my-project
// src/app.js
const express = require('express');
const app = express();
// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Routes
app.use('/api', require('./routes/api'));
This setup creates a modular project with separate routes and middleware. As your application grows, you can easily add new features without disrupting existing functionality.
// Folder structure
/my-express-app
├── src/
│ ├── app.js
│ ├── routes/
│ ├── middleware/
│ └── config/
├── package.json
├── .env
└── README.md
By following these best practices, you'll create maintainable and scalable Express projects. Proper file organization, using middleware effectively, and adhering to security guidelines will ensure your API is robust and performant.
Welcome to the chapter on Middleware & Routing in Node.js! In this section, you'll learn about custom middleware, organizing routes with `express.Router`, and how to structure your API endpoints effectively.
Middleware functions are used in Express.js to perform tasks such as: authenticating requests, logging information, managing errors, and processing the request before it reaches your routes.
const express = require('express');
const app = express();
// Middleware to log requests
app.use((req, res, next) => {
console.log(`Incoming request: ${req.method} ${req.url}`);
next();
});
Custom middleware can be created to handle specific tasks. It's important to remember to call `next()` when passing control to the next middleware in the stack.
// Synchronous middleware
function checkAuth(req, res, next) {
if (req.headers.authorization === 'Bearer valid-token') {
next();
} else {
res.status(401).send('Unauthorized');
}
}
// Asynchronous middleware with Promises
async function logRequest(req, res, next) {
try {
await someAsyncOperation();
console.log(`Logged request: ${req.url}`);
next();
} catch (error) {
res.status(500).send('Internal Server Error');
}
}
The `express.Router()` object is used to create modular route handlers. This makes your code more organized and easier to maintain.
// Routes for /api/users
const router = express.Router();
router.get('/', (req, res) => {
res.send('Get all users');
});
router.post('/', (req, res) => {
res.send('Create a new user');
});
// Mount the router on /api/users
app.use('/api/users', router);
Middleware can be added at a route level to handle specific functionality for that endpoint.
app.get('/api/data', checkAuth, (req, res) => {
res.send('Protected data');
});
Middleware is essential for creating robust APIs. For example, you might use middleware to: validate tokens, compress responses, or handle CORS requests.
When building APIs with Express, ensuring data integrity is critical for security, reliability, and maintaining trust with your users. Data validation involves checking that incoming requests meet specific criteria before processing them. Sanitization ensures that any user input is cleaned or modified to prevent malicious attacks like SQL injection or XSS vulnerabilities.
Joi is a popular library for schema validation. It provides a powerful, flexible way to define and validate data structures.
npm install joi
const Joi = require('joi');
// Create a schema
const userSchema = Joi.object({
name: Joi.string().min(3).required(),
email: Joi.string().email().required(),
age: Joi.number().integer().min(18)
});
app.post('/users', (req, res) => {
const { error } = userSchema.validate(req.body);
if (error) {
return res.status(400).json({
message: 'Validation failed',
details: error.details
});
}
// Proceed with creating the user
});
Zod is a TypeScript-first schema declaration and validation library. It provides an alternative syntax that can be more concise and integrates well with TypeScript.
npm install zod
import { z } from 'zod';
// Create a schema
const userSchema = z.object({
name: z.string().min(3),
email: z.string().email(),
age: z.number().int().min(18)
});
app.post('/users', (req, res) => {
try {
const validatedData = userSchema.parse(req.body);
// Proceed with creating the user using validatedData
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({
message: 'Validation failed',
details: error.issues
});
}
}
});
Express-validator is a middleware library that provides validation utilities for Express routes. It integrates seamlessly with Express and supports both synchronous and asynchronous validation.
npm install express-validator
const { check, validationResult } = require('express-validator');
app.post('/users', [
check('name').not().isEmpty().withMessage('Name is required')
.isLength({ min: 3 }).withMessage('Name must be at least 3 characters long'),
check('email').not().isEmpty().withMessage('Email is required')
.isEmail().withMessage('Invalid email format'),
check('age').not().isEmpty().withMessage('Age is required')
.isInt({ min: 18 }).withMessage('Must be at least 18 years old')
], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
message: 'Validation failed',
details: errors.array()
});
}
// Proceed with creating the user
});
Sanitization ensures that any user input is safe to use. This includes removing or modifying unsafe characters, encoding special characters, and ensuring data is in a usable format.
app.use(express.urlencoded({ extended: true }));
// Use express-validator's sanitize methods
app.post('/users', [
check('name').trim().escape(),
check('email').normalizeEmail(),
check('bio').stripLowSpecialChars()
], (req, res) => {
// Process sanitized data
});
Welcome to the world of authentication and authorization in Express! Today, we'll explore how to implement secure authentication mechanisms and control access to your API endpoints. We'll cover sessions, JWT (JSON Web Tokens), OAuth2, and role-based access control (RBAC).
Authentication is the process of verifying the identity of a user or client. It answers the question: Who are you? In Express, we can implement authentication using several methods.
Authorization is the process of determining what resources a user or client is allowed to access. It answers the question: What are you allowed to do? We'll explore how to implement role-based access control (RBAC).
Sessions are a common method of authentication where user data is stored on the server and accessed via a session ID.
const express = require('express');
const session = require('express-session');
const app = express();
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: true,
cookie: {
secure: true,
httpOnly: true,
sameSite: 'lax'
}
}));
JSON Web Tokens (JWT) are a popular method for token-based authentication. They allow users to authenticate without maintaining sessions on the server.
const express = require('express');
const jwt = require('jsonwebtoken');
app.post('/login', async (req, res) => {
const user = await User.findOne({ email: req.body.email });
if (!user || !await user.comparePassword(req.body.password)) {
return res.status(401).json({ message: 'Invalid credentials' });
}
const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET, {
expiresIn: 86400 // 24 hours
});
res.json({ token });
});
OAuth2 is a delegate authentication framework that allows users to authenticate using third-party accounts (e.g., Google, GitHub).
const express = require('express');
const passport = require('passport');
const GitHubStrategy = require('passport-github2').Strategy;
passport.use(new GitHubStrategy({
clientID: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
}, (accessToken, refreshToken, profile, done) => {
User.findOrCreate({ githubId: profile.id }, (err, user) => {
done(err, user);
});
}));
RBAC allows users to access resources based on their roles and permissions. We'll implement this using middleware.
const express = require('express');
app.use((req, res, next) => {
const userRoles = req.user.roles || [];
if (!userRoles.includes('admin') && req.path.startsWith('/admin')) {
return res.status(403).json({ message: 'Forbidden' });
}
next();
});
Question 1 of 21