Introduction: Setting the Stage for a Task Manager API

Welcome to the first lesson of our course on building a Task Manager API with Next.js!

Unlike earlier courses, where we built projects incrementally — starting with all logic in the API routes, then introducing validation, and finally extracting logic into a service layer — this project reflects a more realistic and production-ready approach from the beginning. Since you've already practiced each of these patterns individually, this time we'll integrate them immediately and treat the project structure as if you're working on a real-world team project.

Think of this course as a "capstone" for your previous lessons. We'll skip re-explaining the basics like how API routes work or how to validate inputs from scratch — instead, we'll immediately apply those patterns to build a clean, modular backend. If you ever feel lost, you can always revisit earlier modules where we broke each piece down in more detail.

Our Task Data and Project Structure

Before we dive into the service layer, let’s quickly review the basic setup for our project. Here’s a summary of how we define a task and where we store our data:

  • Task Type: This defines what a task looks like. Each task has an id, title, content, completed status, and an optional dueDate.
  • In-Memory Data Store: We use a simple array called tasks to keep track of all our tasks. This is just for learning — real apps use databases, but this keeps things simple for now.

Our files are organized so that all task-related logic lives in the src/lib folder, and our API routes will use this logic.

Understanding the Service Layer

A service layer is a part of your backend code that handles the main logic for your application. Think of it as the “brain” that knows how to create, read, update, and delete tasks. The service layer sits between your data (the tasks array) and your API routes.

Why use a service layer?

  • Separation of concerns: Keeps your code organized by separating business logic from request handling.
  • Reusability: You can use the same logic in different parts of your app.
  • Easier to test and maintain: If you need to change how tasks are managed, you only update the service layer.

In our project, the service layer will be a set of functions that work with the tasks array.

Building Task Service Functions (with Examples)

Let’s build the main functions for our service layer. These functions will help us manage tasks in our in-memory store.

Let’s break down what each function does:

  • getAllTasks: Returns the full list of tasks.
  • createTask: Adds a new task to the list. It automatically assigns a unique id and sets completed to false.
  • getTaskById: Finds a task by its id. Returns null if not found.
  • updateTaskById: Updates a task’s details. It can do a full update or a partial update (only the fields you provide).
How the Service Layer Connects to API Routes

Now that you have a service layer, your API routes can use these functions to handle requests. This keeps your route code simple and focused on handling HTTP requests and responses.

Example: Using the service layer in an API route

Here, the API route does not need to know how tasks are stored or managed. It just calls the service layer functions and returns the result. For example, the POST route validates the input and then calls the createTask service function. The input validation uses const errors: string[] = [] to collect multiple validation issues before returning a response. While we've covered this pattern in earlier courses, it's worth noting that this allows us to report all issues at once, instead of stopping at the first error.

Working with Task Routes by ID (PUT, PATCH, DELETE)

So far, we’ve used the src/app/api/tasks/route.ts file to list all tasks or create new ones. But when you want to work with an individual task — such as reading, updating, or deleting it — you use dynamic API routes like src/app/api/tasks/[id]/route.ts.

Each function here (GET, PUT, PATCH, DELETE) calls the relevant service function. Most notably, updateTaskById is reused for both PUT and PATCH, and we control the behavior using the partial flag.

PATCH vs PUT

This lets us reuse the same internal logic for both types of update operations, which keeps our service layer clean and DRY.

** GET and DELETE ** The GET handler fetches a single task by ID. If the task doesn't exist, it returns a 404 using createErrorResponse. The DELETE handler removes a task and returns a 204 No Content if successful — otherwise, it also returns a 404.

Summary And What’s Next

In this lesson, you learned:

  • What a service layer is and why it’s important for organizing backend code.
  • How to define and use service functions to manage tasks in your project.
  • How API routes can use the service layer to keep code clean and easy to maintain.

You now have a solid foundation for your Task Manager API. In the next set of exercises, you’ll get hands-on practice using and extending these service functions. This will help you become comfortable with the service layer pattern and prepare you for building more advanced features.

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