Before we dive into implementing specific routes, let's understand what request and response objects are in Express. Think of them as two sides of a conversation:
The request object (req) is like a letter you receive. It contains all the information sent by the client:
req.params // URL parameters (like /users/:id)
req.query // Query string parameters (?name=value)
req.body // Data sent in request body
req.method // HTTP method used (GET, POST, etc)
req.path // URL path
The response object (res) is like writing a reply. It provides methods to send data back to the client:
res.send() // Send a response (auto-detects type)
res.json() // Send JSON response
res.status() // Set HTTP status code
res.type() // Set content type
Let's start with a simple route that returns a version number. This teaches us about basic text responses.
// GET /version - Return API version
app.get('/version', (req, res) => {
// Send plain text response
res.type('text').send('1.0.0');
});
Here's what's happening in this code:
1. app.get defines a route for GET requests to '/version'
2. We set the content type to text using res.type('text')
3. We send the version string using res.send()
This phase introduces route parameters and JSON responses. Think of route parameters like variables in your URL path.
// GET /viewers/:id - Return user profile
app.get('/viewers/:id', (req, res) => {
// Create user profile with route parameter
const userProfile = {
id: req.params.id,
firstName: "Jane",
lastName: "Doe",
birthDate: "01/01/1990",
favoriteMovies: [
"The Matrix",
"Inception",
"Interstellar"
]
};
// Send JSON response
res.json(userProfile);
});
Key concepts demonstrated here:
1. Route parameters (:id) capture values from the URL
2. req.params.id accesses the captured value
3. res.json() automatically sets content-type and stringifies the object
Query parameters are like optional extras in your request, added after a ? in the URL. This phase teaches error handling and conditional responses.
// GET /info?message=hello - Echo message parameter
app.get('/info', (req, res) => {
// Extract message from query parameters
const message = req.query.message;
// Check if message exists and isn't empty
if (!message) {
return res.type('text').send('message required');
}
// Echo back the message
res.type('text').send(message);
});
Important concepts:
1. req.query accesses URL query parameters
2. Conditional logic handles missing/empty parameters
3. Early return pattern for error cases
This phase combines multiple concepts and adds data transformation.
// POST /movies - Create movie entry
app.post('/movies', (req, res) => {
// Generate random ID (in real apps, this would come from a database)
const movieId = Math.floor(Math.random() * 10000000);
// Transform request data
const movie = {
id: movieId,
name: req.body.name,
year: parseInt(req.body.year, 10),
isFavorite: req.body.favorite === 'on'
};
// Send transformed data as JSON response
res.json(movie);
});
Advanced concepts demonstrated:
1. POST request handling with request body
2. Data transformation (string to number, checkbox to boolean)
3. Object construction and response formatting
Serving static files introduces the concept of middleware and file system integration.
// Configure static file serving
app.use(express.static('public'));
This simple line does a lot:
1. Automatically serves files from the 'public' directory
2. Handles proper content-type setting
3. Manages file streaming and caching
Each phase includes test specifications that guide our implementation. Let's look at how to read and use these tests:
Phase 1 Test Example:
describe("GET /version", () => {
it("returns a text body of '1.0.0'", async () => {
await chai
.request(server)
.get("/version")
.then(res => {
expect(res).to.have.status(200);
expect(res).to.have.header('content-type', /^text/);
expect(res.text).to.eq("1.0.0");
});
});
});
This test tells us three important things about our route:
1. The response status should be 200 (OK)
2. The content-type should be text
3. The response body should exactly match "1.0.0"
When working with Express request/response objects, watch out for:
1. Forgetting to parse request bodies (express.json() middleware)
2. Sending multiple responses to the same request
3. Not handling error cases
4. Mixing up req.params and req.query
Follow these guidelines for clean, maintainable Express routes:
1. Always set appropriate content types
2. Use meaningful status codes
3. Handle all error cases explicitly
4. Validate input data
5. Use middleware for common operations
These concepts form the foundation of real-world API development:
1. User profile APIs use route parameters to fetch specific users
2. Search functionality often uses query parameters
3. Form submissions process POST request bodies
4. File hosting uses static file serving
To deepen your understanding, try these extensions:
1. Add input validation middleware
2. Implement error handling middleware
3. Create nested routes
4. Add authentication middleware
5. Implement file upload handling