Introduction: Serving Data with Next.js API Routes

Welcome back! In the last lesson, you learned how to read query parameters from incoming requests to make your API more dynamic. Now, let’s take the next step: serving a list of data from your API route. This is a common task in web development — whether you’re building a social media app, an online store, or a dashboard, you’ll often need to send lists of users, products, or other items to the frontend.

In this lesson, you will learn how to serve a list of users from a mock database, filter that list using query parameters, and return a well-structured JSON response. By the end, you’ll be able to create an API route that returns useful data in a format that’s easy to work with.


Quick Recap: Mock Data And Project Structure

Before we dive in, let’s quickly remind ourselves about the mock data setup. So far, you’ve returned simple static or dynamic responses. In this lesson, you’ll work with a small dataset and learn how to send real data from your API. For this lesson, we’ll use a file called src/lib/data.ts that acts as our mock database. Here’s what it looks like:

You can import this data into your API route using:

This setup allows you to work with a list of users as if you were using a real database, but everything stays simple and local for now.


Creating The Users API Route

Let’s create an API route that serves the full list of users. In Next.js, you place your API route in the src/app/api/users/route.ts file. Here’s how you can set up a basic GET handler:

Explanation:

  • We import NextResponse from Next.js to help us send JSON responses.
  • We import our users array from the mock data file.
  • The GET function handles GET requests to /api/users.
  • We return the list of users as a JSON object.

🧠 This is an example of a read-only route, where your server simply sends back data and doesn’t modify anything. In this setup, users is the complete dataset, so we don’t need to check for parameters yet. The result is deterministic: the same every time unless the code changes

Sample Output:
When you visit /api/users, you’ll get a response like:


Filtering Data With Query Parameters

Now, let’s make our API more flexible by allowing users to filter the list. For example, you might want to see only users with the role "admin" or only those who are active.

Here’s how you can read query parameters and filter the data:

Explanation:

  • We use the URL object to parse the request URL and get query parameters. The URL object is built into JavaScript and allows you to easily extract different parts of a URL.
  • If a role parameter is present, we filter users by their role. The filter() method goes through every user and checks if their user.role matches the value from the URL. To make sure it’s case-insensitive (so "Admin" and "admin" both work), we convert both sides to lowercase before comparing.
  • If an active parameter is present, we filter users by their active status. This filter works a bit differently. In the mock data, each user has a boolean field isActive which is either true or false. But query parameters are always strings, so if a client sends , we manually convert that string to a boolean.
Building A Useful API Response

A good API response often includes more than just the data. It can also include metadata, such as the total number of results, the filters used, and a timestamp. This makes it easier for the frontend to display information and for developers to debug.

Here’s how you can structure your response:

Explanation:

  • We add a metadata object with the total number of users, the current timestamp, and the filters used.
  • We also include the endpoint, method, and status for clarity.
  • This structure helps anyone using your API understand what data they’re getting and why.

Sample Output:

A response like this is often called a "wrapped" response, because your data is enclosed inside a larger object that includes context (metadata, status, etc.). This format makes the API more frontend-friendly. For example, the frontend can:

  • Display the number of filtered users without re-counting
  • Show which filters were applied
  • Debug unexpected outputs using timestamps or endpoint metadata
What is metadata?

Metadata means “data about data.” It’s extra information that describes or gives context to your main data (in this case, the filtered list of users).

For example:

  • total shows how many users matched the filters
  • timestamp tells when the data was served
  • filters show what criteria were applied (if any)

This helps developers or frontend apps understand:

  • What was returned (user list)
  • Why that list looks the way it does (filters used)
  • When it was created (timestamp)

Instead of returning just the list, adding metadata makes the API response more transparent, debuggable, and easier to work with on the frontend.

Summary And Practice Preview

In this lesson, you learned how to serve a list of users from a mock database using a Next.js API route. You saw how to filter the data using query parameters and how to build a clear, useful API response with metadata. These are essential skills for building real-world APIs that are both flexible and easy to use.

You’ll use this “filter and respond” pattern often — not just for users, but for things like products (/api/products?category=shoes), blog posts (/api/posts?author=john), or even search results. It’s a powerful pattern that lets your frontend ask specific questions — and your backend give focused answers.

Next, you’ll get a chance to practice what you’ve learned. The upcoming exercises will help you reinforce these concepts by having you build and test your own API routes. This hands-on practice will prepare you for more advanced backend development tasks with Next.js. Keep up the great work!

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