
How to Deploy a Next.js API to Production using Sevalla
How to Deploy a Next.js API to Production using Sevalla êŽë š
When people hear about Next.js, they often think of server-side rendering, React-powered frontends, or SEO-optimised static websites. But there's more to this powerful framework than just front-end development.
Next.js also allows developers to build robust, scalable backend APIs directly inside the same codebase. This is especially valuable for small to mid-sized applications where having a tightly coupled frontend and backend speeds up development and deployment.
In this article, youâll learn how to build an API using Next.js and deploy it to production using Sevalla. Itâs relatively easy to learn how to build something using a tutorial â but the real challenge is to get it into the hands of users. Doing so transforms your project from a local prototype into something real and usable.
What is Next.js?
Next.js is an open-source React framework built by Vercel. It enables developers to build server-rendered and statically generated web applications.It essentially abstracts the configuration and boilerplate needed to run a full-stack React application, making it easier for developers to focus on building features rather than setting up infrastructure.
While it started as a solution for frontend challenges in React, it has evolved into a full-stack framework that lets you handle backend logic, interact with databases, and build APIs. This unified codebase is what makes Next.js particularly compelling for modern web development.
Installation & Setup
Letâs install Next.js. Make sure you have Node.js and NPM installed on your system, and that theyâre the latest version.
node --version
# v22.16.0
npm --version
# 10.9.2
Now letâs create a Next.js project. The command to do so is:
npx create-next-app@latest
The result of the above command will ask you a series of questions to setup your app:
What is your project named? my-app
Would you like to use TypeScript? No / Yes
Would you like to use ESLint? No / Yes
Would you like to use Tailwind CSS? No / Yes
Would you like your code inside a `src/` directory? No / Yes
Would you like to use App Router? (recommended) No / Yes
Would you like to use Turbopack for `next dev`? No / Yes
Would you like to customize the import alias (`@/*` by default)? No / Yes
What import alias would you like configured? @/*
But for this tutorial, we arenât interested in a full stack app â just an API. So letâs re-create the app using the --api
flag.
npx create-next-app@latest --api
It will still ask you a few questions. Use the default settings and finish creating the app.

Once the setup is done, you can see the folder with your app name. Letâs go into the folder and run the app.
npm run dev
Your API template should be running at port 3000. Go to http://localhost:3000
and you should see the following message:
{
"message": "Hello world!"
}
How to Build a REST API
Now that weâve set up our API template, let's write a basic REST API. A basic REST API is simply four endpoints: Create, Read, Update, Delete (also called as CRUD).
Usually, weâd use a database, but for simplicityâs sake, weâll use a JSON file in our API. Our goal is to build a REST API that can read and write to this JSON file.
The API code will reside under /app
within the project directory. Next.js uses file-based routing for building URL paths.
For example, if you want a URL path /users
, you should have a directory called âusersâ with a route.ts file to handle all the CRUD operations for /users
. For /users/:id
, you should have a directory called [id]
under âusersâ directory with a route.ts file. The square brackets are to tell Next.js that you expect dynamic values for the /users/:id
route.
You should also have the users.json inside the /app/users
directory for your routes to read and write data.
Here is a screenshot of the setup. Delete the [slug]
directory that comes with the project since it wonât be relevant for us:

- The route.ts file at the bottom handles CRUD operations for
/
- The route.ts file under
/users
handles CRUD operations for/users
- The route.ts file under
/users/[id]/
handles CRUD operations under/users/:id
where the âidâ will be a dynamic value. - The
users.json
under/users
will be our data store.
While this setup can seem complicated for a simple project, it provides a clear structure for large-scale web applications. If you want to go deeper into building complex APIs with Next.js, here is a tutorial you can follow.
The code under /app/
route.ts
is the default file for our API. You can see it serving the GET request and responding with âHello World!â:
import { NextResponse } from "next/server";
export async function GET() {
return NextResponse.json({ message: "Hello world!" });
}
Now we need five routes:
- GET /users â List all users
- GET
/users/:id
â List a single user - POST /users â Create a new user
- PUT
/users/:id
â Update an existing user - DELETE
/users/:id
â Delete an existing user
Here is the code for the route.ts
file under /app/users
:
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { promises as fs } from "fs"; // Importing promise-based filesystem methods
import path from "path"; // For handling file paths
// Define the structure of a User object
interface User {
id: string;
name: string;
email: string;
age: number;
}
// Define the path to the users.json file
const usersFile = path.join(process.cwd(), "app/users/users.json");
// Read users from the JSON file and return them as an array
async function readUsers(): Promise<User[]> {
try {
const data = await fs.readFile(usersFile, "utf-8");
return JSON.parse(data) as User[];
} catch {
// If file doesn't exist or fails to read, return empty array
return [];
}
}
// Write updated users array to the JSON file
async function writeUsers(users: User[]) {
await fs.writeFile(usersFile, JSON.stringify(users, null, 2), "utf-8");
}
// Handle GET request: return list of users
export async function GET() {
const users = await readUsers();
return NextResponse.json(users);
}
// Handle POST request: add a new user
export async function POST(request: NextRequest) {
const body = await request.json();
// Destructure and validate input fields
const { name, email, age } = body as {
name?: string;
email?: string;
age?: number;
};
// Return 400 if any required field is missing
if (!name || !email || age === undefined) {
return NextResponse.json(
{ error: "Missing name, email, or age" },
{ status: 400 }
);
}
// Read existing users
const users = await readUsers();
// Create new user object with unique ID based on timestamp
const newUser: User = {
id: Date.now().toString(),
name,
email,
age,
};
// Add new user to the list and save to file
users.push(newUser);
await writeUsers(users);
// Return the newly created user with 201 Created status
return NextResponse.json(newUser, { status: 201 });
}
Now the code for the /app/users/[id]/
route.ts
file:
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { promises as fs } from "fs";
import path from "path";
// Define the User interface
interface User {
id: string;
name: string;
email: string;
age: number;
}
// Path to the users.json file
const usersFile = path.join(process.cwd(), "app/users/users.json");
// Function to read users from the JSON file
async function readUsers(): Promise<User[]> {
try {
const data = await fs.readFile(usersFile, "utf-8");
return JSON.parse(data) as User[];
} catch {
// If file doesn't exist or is unreadable, return an empty array
return [];
}
}
// Function to write updated users to the JSON file
async function writeUsers(users: User[]) {
await fs.writeFile(usersFile, JSON.stringify(users, null, 2), "utf-8");
}
// GET `/users/:id` - Fetch a user by ID
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> },
) {
const id = (await params).id;
const users = await readUsers();
// Find the user by ID
const user = users.find((u) => u.id === id);
// Return 404 if user is not found
if (!user) {
return NextResponse.json({ error: "User not found" }, { status: 404 });
}
// Return the found user
return NextResponse.json(user);
}
// PUT `/users/:id` - Update a user by ID
export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> },
) {
const id = (await params).id;
const body = await request.json();
// Extract optional fields from request body
const { name, email, age } = body as {
name?: string;
email?: string;
age?: number;
};
const users = await readUsers();
// Find the index of the user to update
const index = users.findIndex((u) => u.id === id);
// Return 404 if user not found
if (index === -1) {
return NextResponse.json({ error: "User not found" }, { status: 404 });
}
// Update the user only with provided fields
users[index] = {
...users[index],
...(name !== undefined ? { name } : {}),
...(email !== undefined ? { email } : {}),
...(age !== undefined ? { age } : {}),
};
await writeUsers(users);
// Return the updated user
return NextResponse.json(users[index]);
}
// DELETE `/users/:id` - Delete a user by ID
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> },
) {
const id = (await params).id;
const users = await readUsers();
// Find the index of the user to delete
const index = users.findIndex((u) => u.id === id);
// Return 404 if user not found
if (index === -1) {
return NextResponse.json({ error: "User not found" }, { status: 404 });
}
// Remove user from the array and save updated list
const [deleted] = users.splice(index, 1);
await writeUsers(users);
// Return the deleted user
return NextResponse.json(deleted);
}
We will have an empty array inside the /app/users.json. You can find all the code here in this repository (manishmshiva/nextjs-api
).
How to Test the API
Now letâs test the API endpoints.
First, lets run the API:
npm run dev
You can go to http://localhost:3000/users
and can see an empty array since we have not pushed any user information.
From the code, we can see that a user object needs name, email, and age since the id is automatically generated in the POST endpoint.

We will use Postman to simulate requests to the API and ensure that the API behaves as expected.
1. GET /users
it will be empty on our first try since we havenât pushed any data yet.

2. POST /users
create a new user. Under âbodyâ, choose ârawâ and select âJSONâ. This is the data we will be sending the api. The JSON body would be
{"name":"Manish","age":30, "email":"manish@example.com"}

Iâll create one more record named âLarryâ. Here is the JSON:
{"name":"Larry","age":25, "email":"larrry@example.com"}
Now letâs look at the users. You should see two entries for our GET request to /users
:

/users
Now letâs look at a single user using /users/:id
.

/users/:id
Now letâs update Larryâs age to 35. Weâll pass just the age in request body using the PUT request to /users/:id
.

/users/:id
Now letâs delete Larryâs record.

/users/:id
If you check /users
, you should see only one record:

/users
So we have built and tested our api. Now letâs get this live.
How to Deploy to Sevalla
Sevalla is a modern, usage-based Platform-as-a-service provider and an alternative to sites like Heroku or to your self-managed setup on AWS. It combines powerful features with a smooth developer experience.
Sevalls offers application hosting, database, object storage, and static site hosting for your projects. It comes with a generous free tier, so letâs see how to deploy our API to the cloud using Sevalla.
Make sure you have the code committed to GitHub or fork my repository (manishmshiva/nextjs-api
) for this project. If you are new to Sevalla, you can sign up using your GitHub account to enable direct deploys from your GitHub account. Every time you push code to your project, Sevalla will auto-pull and deploy your app to the cloud.
Once you login to Sevalla, click on âApplicationsâ. Now letâs create an app.

If you have authenticated with GitHub, the application creation interface will display a list of repositories. Choose the one you pushed your code into or the nextjs-api project if you forked it from my repository.

Check the box âauto deploy on commitâ. This will ensure your latest code is auto-deployed to Sevalla. Now, letâs choose the instance to which we can deploy the application. Each one comes with its own pricing, based on the server's capacity.
Letâs choose the hobby server that costs $5/mo. Sevalla gives us a $50 free tier, so we donât have to pay for anything unless we exceed this usage tier.

Now, click âCreate and Deployâ. This should pull our code from our repository, run the build process, setup a Docker container and then deploy the app. Usually the work of a sysadmin, fully automated by Sevalla.
Wait for a few minutes for all the above to happen. You can watch the logs in the âDeploymentsâ interface.

Now, click on âVisit Appâ and you will get the live URL (ending with sevalla.app
) of your API. You can replace http://localhost:3000
with the new URL and run the same tests using Postman.
Congratulations â your app is now live. You can do more with your app using the admin interface, like:
- Monitor the performance of your app
- Watch real-time logs
- Add custom domains
- Update network settings (open/close ports for security, and so on)
- Add more storage
Sevalla also provides resources like Object storage, database, cache, and so on, which are out of scope for this tutorial. But it lets you monitor, manage, and scale your application without the need for a system administrator. Thatâs the beauty of PaaS systems. Here is a detailed comparison of VPS vs PaaS systems for application hosting.
Conclusion
In this article, we went beyond the typical frontend use case of Next.js and explored its capabilities as a full-stack framework. We built a complete REST API using the App Router and file-based routing, with data stored in a JSON file. Then, we took it a step further and deployed the API to production using Sevalla, a modern PaaS that automates deployment, scaling, and monitoring.
This setup demonstrates how developers can build and ship full-stack applications like frontend, backend, and deployment, all within a single Next.js project. Whether you're prototyping or building for scale, this workflow sets you up with everything you need to get your apps into usersâ hands quickly and efficiently.
Hope you enjoyed this article. I ll see you soon with another one. Connect with me on LinkedIn or visit my website (manishmshiva
).