Welcome: From “It Works” to “It’s Safe to Use”

Welcome back. In the previous lesson, you used Codex to design the Task core: a shared Task model, an in-memory tasks array, and a service layer that knows how to create, read, update, and delete tasks. Your API can now do real work—but so far it mostly assumes that incoming JSON is correct.

In this lesson, you’ll upgrade your Task Manager API from “accept anything and hope” to “trust but verify.” You’ll:

  • Build a central task payload validator in src/lib/validateTaskPayload.ts
  • Plug that validator into updateTaskById in src/lib/services/taskService.ts
  • Wire validation into PUT and PATCH for src/app/api/tasks/[id]/route.ts, returning proper HTTP status codes

You’ll continue to rely on Codex for implementation, but now your prompts will describe more nuanced rules: allowed fields, required fields, strict types, and how to propagate errors back to the client.

What We’re Building and Why It Matters

So far, your API can:

  • Store tasks in memory
  • Expose /api/tasks and /api/tasks/[id] routes
  • Perform basic CRUD via the service layer

What it does not do yet is enforce the rules of your domain. Examples:

  • A client could send "completed": "yes" instead of a boolean.
  • A task update could include random fields like "hack": "oops".
  • A PUT could omit required fields entirely.

Without validation, bad data sneaks into your in-memory store and everything built on top becomes fragile.

This lesson introduces a clear separation of concerns:

  • validateTaskPayload checks if incoming data is shaped correctly.
  • taskService.updateTaskById decides whether to mutate state based on validator results.
  • The /api/tasks/[id] route translates service results into HTTP responses: 200, 400, 404, 204.

Once this pipeline is in place, every future feature you add (logging, persistence, UI) can assume the data is clean.

How We’ll Use Codex in This Lesson

You’ll use Codex in three focused ways:

  • To implement the reusable validator

    • One file: src/lib/validateTaskPayload.ts
    • Well-defined function: validateTaskPayload(payload: any, partial = false): string[]
    • Return array of human-readable error messages
  • To integrate validation into the service layer

    • One function: updateTaskById in src/lib/services/taskService.ts
    • Call validateTaskPayload and decide whether to update or return an error object
  • To enforce validation in PUT/PATCH routes

    • File: src/app/api/tasks/[id]/route.ts
    • Handlers use updateTaskById and map { error } / { task } to HTTP status codes

Across all three, your prompts should:

  • Restrict Codex to one file at a time
  • Describe allowed fields, required fields, and type expectations clearly
  • Explain how to treat partial updates vs full replacements
  • Ask Codex to show the full updated file content
Step 1: Building a Shared Task Payload Validator

Your first job is to give the backend a single place that knows what a valid task payload looks like. The starter file already tells Codex what to do, but you’ll turn that into a concrete implementation:

Conceptually, validateTaskPayload should:

  • Ensure the payload is a non-null object (no arrays, no primitives)
  • Allow only title, content, completed, and dueDate
  • When partial is false (full update), require title, content, and completed
  • Enforce types:
    • title / content → strings
    • completed → boolean
Step 2: Validating Updates in the Service Layer

With the validator in place, you’ll make sure the service layer refuses invalid updates before they touch the in-memory tasks array.

The starter updateTaskById already hints at what should happen (as shown in src/lib/services/taskService.ts).

The core behavior you want is:

  • Find the task by id.
  • If not found, return { error: 'Task not found' }.
  • Validate the incoming data with validateTaskPayload(data, partial).
  • If there are errors, join them into a single string (e.g. errors.join('; ')) and return { error: combined }.
  • Do not mutate the task when validation fails.
  • Apply the update only when validation passes:
    • For partial updates (partial = true): merge only the provided fields.
    • For full updates (partial = false): treat the payload as a replacement for title, content, completed, and dueDate.
  • Return the updated task as .
Step 3: Enforcing Validation in PUT/PATCH /api/tasks/[id]

The last step is to wire everything into the item-level route so HTTP callers see the right status codes and messages.

You’ll instruct Codex to:

  • Parse id from context.params.id as a number
  • Use getTaskById to implement GET with 200 / 404
  • Use updateTaskById to implement PUT and PATCH with validation-aware behavior
    • PUT uses partial = false (full replacement)
    • PATCH uses partial = true (partial update)
  • Map service results to HTTP codes:
    • If { error: 'Task not found' }404
    • If { error: '...' } for validation issues → 400
    • If { task }200 with the updated task
  • Use deleteTaskById for :
Summary and What’s Next

In this lesson, you:

  • Created a central task validator that enforces shape and type rules for title, content, completed, and dueDate.
  • Hooked validateTaskPayload into updateTaskById so invalid updates never reach your in-memory store.
  • Translated service-level results into clear HTTP responses in PUT and PATCH (and fully coded GET/DELETE) for /api/tasks/[id].

You now have an API that not only performs CRUD, but also defends its own data and reports errors clearly.

In upcoming lessons, you’ll continue refining your Task Manager API—evolving response patterns, improving error consistency, and preparing the backend for more realistic production-style scenarios, all while guiding Codex with increasingly precise prompts.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal