Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      Error’d: Pickup Sticklers

      September 27, 2025

      From Prompt To Partner: Designing Your Custom AI Assistant

      September 27, 2025

      Microsoft unveils reimagined Marketplace for cloud solutions, AI apps, and more

      September 27, 2025

      Design Dialects: Breaking the Rules, Not the System

      September 27, 2025

      Building personal apps with open source and AI

      September 12, 2025

      What Can We Actually Do With corner-shape?

      September 12, 2025

      Craft, Clarity, and Care: The Story and Work of Mengchu Yao

      September 12, 2025

      Cailabs secures €57M to accelerate growth and industrial scale-up

      September 12, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      Using phpinfo() to Debug Common and Not-so-Common PHP Errors and Warnings

      September 28, 2025
      Recent

      Using phpinfo() to Debug Common and Not-so-Common PHP Errors and Warnings

      September 28, 2025

      Mastering PHP File Uploads: A Guide to php.ini Settings and Code Examples

      September 28, 2025

      The first browser with JavaScript landed 30 years ago

      September 27, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured
      Recent
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»Development»How to Deploy a Next.js API with PostgreSQL and Sevalla

    How to Deploy a Next.js API with PostgreSQL and Sevalla

    August 18, 2025

    When developers think of Next.js, they often associate it with SEO-friendly static websites or React-based frontends. But what many miss is how Next.js can also be used to build full-featured backend APIs – all within the same project.

    I’ve recently written an article on working with Next.js API and deploying it to production. In this case, I would’ve used a JSON file as a mini-database.

    But JSON or any type of file storage isn’t fit for a production application. This is because file-based storage isn’t designed for concurrent access, so multiple users writing data at the same time can cause corruption or loss.

    It also lacks indexing and query capabilities, making it slow as data grows. Backups, security, and scalability are also harder to manage compared to a proper database.

    In short, while JSON files work for demos or prototypes, production systems need a database that can handle concurrency, large datasets, complex queries, and reliable persistence.

    So in this article, we’ll walk through how to build a REST API with Next.js, store data in a Sevalla-managed database, and deploy the whole project to production using Sevalla’s PaaS infrastructure.

    Table of Contents

    • What is Next.js?

    • Installation and Setup

    • How to Build a NextJS API

    • Provisioning a Database in Sevalla

    • Deploying to Sevalla

    • Conclusion

    What is Next.js?

    Next.js is an open-source React framework developed by Vercel. It’s known for server-side rendering, static generation, and seamless routing. But beyond its frontend superpowers, it allows developers to build backend logic and APIs through its file-based routing system. This makes Next.js a great choice for building full-stack apps.

    Installation and Setup

    To get started, make sure Node.js and NPM are installed.

    $ node --version
    v22.16.0
    
    $ npm --version
    10.9.2
    

    Now, create a new Next.js project:

    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.

    9f1d2763-7df5-491b-8cb3-05161b35fbd9

    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 NextJS API

    Now that we’ve set up our API template, let’s write a basic REST API with two endpoints: one to create data and one to view data

    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.

    Here is a screenshot of the setup. Delete the [slug] directory that comes with the project since it won’t be relevant for us.

    Folder setup

    • The route.ts file at the bottom handles CRUD operations for / (this is where the response “hello world” was generated from)

    • The route.ts file under /users handles CRUD operations for /users

    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 two routes:

    • GET /users which lists all users

    • POST /users which creates a new user

    For this project, we’ll use a database to store our records. We’re not going to install a database on our local machine. Instead, we’ll provision the database in the cloud and use it with our API. This approach is common in test / prod environments to ensure data consistency.

    Provisioning a Database in 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 we’ll use it to connect to a database as well as deploy our app to the cloud.

    If you are new to Sevalla, you can sign up using your GitHub account to enable direct deploys from your GitHub. 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 “Databases”.

    Sevalla Databases

    Now let’s create a PostgreSQL database.

    Create Postgresql Database

    Use the default settings. Once the database is created, it will disable the external connections by default for security to ensure no one outside our server can connect to it. Since we want to test our connection from our local machine, let’s enable an external connection.

    Database settings

    The value we need to connect to the database from our local endpoint is “url” under external connection. Create a file called .env in the project and paste the URL in the below format:

    PGSQL_URL=postgres:<span class="hljs-comment">//<username>:<password>-@asia-east1-001.proxy.kinsta.app:30503/<db_name></span>
    

    The reason we use .env is to store environment variables specific to the environment. In production, we won’t need this file (never push .env files to GitHub). Sevalla will give us the option to add environment variables via the GUI when we deploy the app.

    Now let’s test our database connection. Install the pg package for Node to interact with PostgreSQL. Let’s also install the TypeScript extension for pg to support TypeScript definitions.

    $ npm i pg
    $ npm install --save-dev <span class="hljs-meta">@types</span>/pg
    

    Change the route.ts that served “hello world” to the below:

    <span class="hljs-comment">// app/api/your-endpoint/route.ts</span>
    <span class="hljs-keyword">import</span> { NextResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/server"</span>;
    <span class="hljs-keyword">import</span> { Client } <span class="hljs-keyword">from</span> <span class="hljs-string">"pg"</span>;
    
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">GET</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-keyword">const</span> client = <span class="hljs-keyword">new</span> Client({
        connectionString: process.env.PGSQL_URL,
      });
    
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">await</span> client.connect();
        <span class="hljs-keyword">await</span> client.end();
        <span class="hljs-keyword">return</span> NextResponse.json({ message: <span class="hljs-string">"Connected to database"</span> });
      } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Database connection error:"</span>, error);
        <span class="hljs-keyword">return</span> NextResponse.json({ message: <span class="hljs-string">"Connection failed"</span> }, { status: <span class="hljs-number">500</span> });
      }
    }
    

    Now when your app and go to localhost:3000, it should say “connected to database”.

    Postgresql successful connection

    Great. Now let’s write our two routes, one to create data and the other to view the data we created. Use this code under users/route.ts:

    <span class="hljs-keyword">import</span> { NextResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/server"</span>;
    <span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { NextRequest } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/server"</span>;
    <span class="hljs-keyword">import</span> { Client } <span class="hljs-keyword">from</span> <span class="hljs-string">"pg"</span>;
    
    <span class="hljs-comment">// Define the structure of a User object</span>
    <span class="hljs-keyword">interface</span> User {
      id: <span class="hljs-built_in">string</span>;
      name: <span class="hljs-built_in">string</span>;
      email: <span class="hljs-built_in">string</span>;
      age: <span class="hljs-built_in">number</span>;
    }
    
    <span class="hljs-comment">// Create a PostgreSQL client</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getClient</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Client({
        connectionString: process.env.PGSQL_URL,
      });
    }
    
    <span class="hljs-comment">// Fetch all users from the database</span>
    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">readUsers</span>(<span class="hljs-params"></span>): <span class="hljs-title">Promise</span><<span class="hljs-title">User</span>[]> </span>{
      <span class="hljs-keyword">const</span> client = getClient();
      <span class="hljs-keyword">await</span> client.connect();
    
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> client.query(<span class="hljs-string">"SELECT id, name, email, age FROM users"</span>);
        <span class="hljs-keyword">return</span> result.rows;
      } <span class="hljs-keyword">finally</span> {
        <span class="hljs-keyword">await</span> client.end();
      }
    }
    
    <span class="hljs-comment">// Insert or update users in the database</span>
    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">writeUsers</span>(<span class="hljs-params">users: User[]</span>) </span>{
      <span class="hljs-keyword">const</span> client = getClient();
      <span class="hljs-keyword">await</span> client.connect();
    
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> insertQuery = <span class="hljs-string">`
          INSERT INTO users (id, name, email, age)
          VALUES ($1, $2, $3, $4)
          ON CONFLICT (id) DO UPDATE SET
            name = EXCLUDED.name,
            email = EXCLUDED.email,
            age = EXCLUDED.age;
        `</span>;
    
        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> user <span class="hljs-keyword">of</span> users) {
          <span class="hljs-keyword">await</span> client.query(insertQuery, [user.id, user.name, user.email, user.age]);
        }
      } <span class="hljs-keyword">finally</span> {
        <span class="hljs-keyword">await</span> client.end();
      }
    }
    
    <span class="hljs-comment">// Handle GET request: return list of users</span>
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">GET</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> users = <span class="hljs-keyword">await</span> readUsers();
        <span class="hljs-keyword">return</span> NextResponse.json(users);
      } <span class="hljs-keyword">catch</span> (err) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error reading users from DB:"</span>, err);
        <span class="hljs-keyword">return</span> NextResponse.json({ error: <span class="hljs-string">"Failed to fetch users"</span> }, { status: <span class="hljs-number">500</span> });
      }
    }
    
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">POST</span>(<span class="hljs-params">req: NextRequest</span>) </span>{
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> body = <span class="hljs-keyword">await</span> req.json();
        <span class="hljs-keyword">const</span> users: User[] = <span class="hljs-built_in">Array</span>.isArray(body) ? body : [body];
    
        <span class="hljs-keyword">await</span> writeUsers(users);
    
        <span class="hljs-keyword">return</span> NextResponse.json({ success: <span class="hljs-literal">true</span>, count: users.length });
      } <span class="hljs-keyword">catch</span> (err) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error writing users to DB:"</span>, err);
        <span class="hljs-keyword">return</span> NextResponse.json({ error: <span class="hljs-string">"Failed to write users"</span> }, { status: <span class="hljs-number">500</span> });
      }
    }
    

    Now when you go to localhost:3000/users, it will give you an error because the users table does exist. So let’s create one.

    In the database UI, click on “Studio”. You’ll get a visual editor for your database where you can manage your data directly (pretty cool, right?).

    Database studio

    Press the “+” icon and choose “create table”. Create the table with the schema below. Click the “add column” link to create new columns.

    Database Schema

    Click “create table and you should see the table created as below:

    Users table

    Let’s add a dummy record using “add record” button to use it to test our API. The id field should be in UUID format (and you can generate one here).

    Now let’s test our API.

    3fd9784e-3a83-415d-870f-f3f5d23dec51

    You should see the user you created as the response to the localhost:3000/users query. Now let’s create a new user using our API.

    We’ll use Postman for this since its easy to create POST requests using Postman. We’ll send a sample data under “body” → “raw” → “JSON”.

    Post Request

    The response from Postman should be as below:

    Postman results

    Now going to localhost:3000/users, you should see the new record created.

    Get /users

    Great job! Now let’s get this app live.

    Deploying to Sevalla

    Push your code to GitHub or fork my repository. Now lets go to Sevalla and create a new app.

    Sevalla create app

    Choose your repository from the dropdown and check “Automatic deployment on commit”. This will ensure that the deployment is automatic every time you push code. Choose “Hobby” under the resources section.

    Sevalla Create New App

    Click “Create” and not “Create and deploy”. We haven’t added our PostgreSQL URL as an environment variable, so the app will crash if you try to deploy it.

    Go to the “Environment variables” section and add the key “PGSQL_URL” and the URL in the value field.

    7525c6cd-63af-40b2-80c5-6b49b6101f19

    Now go back to the “Overview” section and click “Deploy now”.

    c3f12f86-0732-4518-bf51-4867ac86abdd

    Once deployment is complete, click “Visit app” to get the live URL of your API. You can replace localhost:3000 with the new URL in Postman and test your API.

    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

    Conclusion

    Next.js is no longer just a frontend framework. It’s a powerful full-stack platform that lets you build and deploy production-ready APIs with minimal friction. By pairing it with Sevalla’s developer-friendly infrastructure, you can go from local development to a live, cloud-hosted API in minutes.

    In this tutorial, you learned how to set up a Next.js API project, connect it to a cloud-hosted PostgreSQL database on Sevalla, and deploy everything seamlessly. Whether you’re building a small side project or a full-scale application, this stack gives you the speed, structure, and scalability to move fast without losing flexibility.

    Hope you enjoyed this article. I’ll see you soon with another one. You can connect with me here or visit my blog.

    Source: freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More 

    Facebook Twitter Reddit Email Copy Link
    Previous ArticleCreating a Real-Time Gesture-to-Text Translator Using Python and Mediapipe
    Next Article How to Build an Always Listening Network Connectivity Checker in Flutter using BLoC

    Related Posts

    Development

    Using phpinfo() to Debug Common and Not-so-Common PHP Errors and Warnings

    September 28, 2025
    Development

    Mastering PHP File Uploads: A Guide to php.ini Settings and Code Examples

    September 28, 2025
    Leave A Reply Cancel Reply

    For security, use of Google's reCAPTCHA service is required which is subject to the Google Privacy Policy and Terms of Use.

    Continue Reading

    FBI, CISA Warn About Scattered Spider Cyberattacks

    Development

    Grab OnePlus’ latest flagship smartwatch for up to $80 off – buy one while the deal lasts

    News & Updates

    Design Systems in 2025: Why They’re the Blueprint for Consistent UX

    Development

    How to use chatgpt4o to redesign your website

    Web Development

    Highlights

    Development

    Take the Annual State of Laravel 2025 Survey

    July 24, 2025

    Take the State of Laravel 2025 survey to contribute your voice to the Laravel community.…

    A glimpse of the next generation of AlphaFold

    May 13, 2025

    CISA Adds Apple and TP-Link Vulnerabilities to KEV Catalog

    June 17, 2025

    Upscayl examples with old photos

    September 13, 2025
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.