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.
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.
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:
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. TheURL
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. Thefilter()
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 fieldisActive
which is eithertrue
orfalse
. But query parameters are always strings, so if a client sends , we manually convert that string to a boolean.
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
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 filterstimestamp
tells when the data was servedfilters
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.
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!
