Understanding Express Request/Response Objects

Understanding the Core Concepts

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

Basic Phase 1: Version Response

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()

Basic Phase 2: User Profile Response

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

Basic Phase 3: Query Parameters

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

Advanced Bonus Phase A: Complex JSON Response

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

Advanced Bonus Phase B: Static Files

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

Understanding Test-Driven Development

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"

Common Pitfalls and Tips

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

Best Practices

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

Real-World Applications

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

Further Learning

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