Phase 2 · Web Servers & HTTP APIsModule 16~90 min read

Phase Project: Build a Task REST API

Combine Phase 2 into a layered, tested Express API with CRUD routes, JSON persistence, validation, structured errors, and documentation.

What you'll learn

The Phase 2 project turns the task tracker into a layered HTTP service. You will combine Express routing, validation, JSON persistence, error contracts, request observability, integration tests, and documentation into one review-ready API.

By the end of this lesson, you'll be able to:

  • Design and implement a complete task REST contract
  • Separate HTTP, application, domain, and persistence boundaries
  • Protect the API with validation and centralized errors
  • Deliver tests, documentation, health signals, and safe lifecycle behavior

Core mental model

Node.js becomes easier when you separate the JavaScript language from the runtime and the operating-system capabilities it exposes. Use this table as a decision guide.

ConceptWhat it meansDecision rule
Composition rootOne place constructs concrete dependencies and the applicationWire repository, service, logger, config, and routes at startup
Service layerCoordinates task rules independent of HTTPAccept ordinary values and throw domain/application errors, not Express responses
Repository contractAbstracts task persistence operationsUse the same contract for JSON storage and in-memory tests
Delivery contractRoutes, statuses, schemas, errors, and headers promised to clientsProtect it with integration tests and OpenAPI

Professional workflow

Build and verify Node.js programs from the terminal in small, observable steps.

  1. Freeze the route/status/schema contract
  2. Define repository and service boundaries
  3. Implement and test task rules
  4. Add a JSON repository with bounded validated data
  5. Create Express routers and validation middleware
  6. Install request context, not-found, and error handling
  7. Add health, shutdown, integration tests, and OpenAPI
  8. Run the full verification workflow from a clean install

Keep the feedback loop short

Run the smallest useful command after every meaningful change. Read the complete error message before editing again, and keep inputs and outputs visible while you learn.

Guided code lab

Keep the service independent of Express

The service coordinates identity, timestamps, and repository persistence using plain JavaScript values.

src/tasks-service.js
import { randomUUID } from "node:crypto";
import { HttpError } from "./http-errors.js";

export function createTasksService({ repository, clock = () => new Date() }) {
  return {
    async create(input) {
      const task = {
        id: randomUUID(),
        title: input.title,
        completed: false,
        createdAt: clock().toISOString(),
      };
      await repository.insert(task);
      return task;
    },
    async find(id) {
      const task = await repository.findById(id);
      if (!task) throw new HttpError(404, "TASK_NOT_FOUND", "Task not found");
      return task;
    },
  };
}

Translate HTTP in a thin router

Validation and service behavior are dependencies; the handler maps the result into the published HTTP contract.

src/tasks-router.js
export function createTasksRouter({ service, validateBody }) {
  const router = Router();

  router.post("/", validateBody(validateNewTask), async (request, response) => {
    const task = await service.create(request.validatedBody);
    response
      .status(201)
      .location("/api/tasks/" + task.id)
      .json({ data: task });
  });

  router.get("/:taskId", async (request, response) => {
    const task = await service.find(request.params.taskId);
    response.json({ data: task });
  });

  return router;
}

Define the clean delivery workflow

A reviewer should be able to reproduce setup and verification with a short stable command sequence.

Terminal
npm ci
npm run check
npm test
npm start

# In another terminal:
curl -i http://127.0.0.1:3000/health/ready
curl -i -X POST http://127.0.0.1:3000/api/tasks   -H "content-type: application/json"   -d '{"title":"Ship Phase 2"}'

Production practice

Protect boundaries

HTTP knows Express, the service knows task operations, and the repository knows persistence. Dependencies point inward through small contracts.

Ship reproducibly

A clean install, one verification command, deterministic tests, and documented environment variables are part of the deliverable.

Design for replacement

The JSON repository is temporary; a narrow contract lets Phase 3 introduce a database without rewriting routes and rules.

Common failure mode

Calling repository methods directly from every route binds HTTP behavior to storage and duplicates validation and not-found rules. Keep a service boundary even while the project is small.

Independent workshop

Complete the Task REST API as a Phase 2 portfolio project ready for review and future database integration.

Your finished workshop must include:

  • GET/POST/PATCH/DELETE task endpoints with filters and pagination
  • Layered app, router, service, and repository modules
  • Bounded JSON parsing and validated inputs
  • Consistent error codes and request IDs
  • Liveness, readiness, and graceful shutdown
  • Unit and integration test coverage
  • OpenAPI 3.1 documentation and curl examples
  • Clean-install and verification instructions

Definition of done

Run the happy path and at least two edge cases, keep responsibilities separated, and add a short README explaining how to run the program.

Recap & quick check

Key takeaways

  • The composition root owns concrete dependencies
  • Services keep business behavior independent of Express
  • Repositories isolate persistence
  • Routers translate the HTTP contract
  • Tests and OpenAPI protect delivery behavior
  • Operational readiness is part of API design
  • Phase 3 can replace JSON with a database behind the same boundary

Quick check

1. Where should concrete dependencies be wired together?

2. Should a task service return Express response objects?

3. What enables replacing JSON persistence later?

4. What protects the public API contract?

Next: Phase 3 — Databases & Persistent Data