Build and Deploy an Express API in 30 Minutes

You can build and deploy an Express API in 30 minutes, even if you have never put a backend online before. An Express API is a small web server, written in JavaScript, that receives requests from apps and answers with data. In this guide, you will create one from scratch. Then you will test it, secure it, push it to GitHub, and publish it on a free host with a public URL.

The pace is realistic because the project stays small. You will build a simple tasks API with five routes, basic validation, and clear error messages. Along the way, you will also learn why each step matters. As a result, you will finish with a working Express API and the confidence to build the next one on your own.

Everything here is current as of October 2026. It uses Node.js 24 LTS and Express 5, which is now the default version on npm. If you can open a terminal and copy a command, you can follow along. Let’s start the clock.

What You Will Build With This Express API Tutorial

Before writing code, it helps to see the destination. By the end, your Express API will manage a list of tasks over HTTP. Any front end, mobile app, or script will be able to create, read, update, and delete those tasks.

Here is the timeline we will follow:

MinutesGoalWhat you will have
0–5Project setupA folder, dependencies, and a running server
5–15RoutesA complete tasks Express API
15–20SafetyValidation, error handling, and security headers
20–23Version controlYour code on GitHub
23–30DeploymentA live Express API on the internet

The data will live in memory to keep things fast. Consequently, your tasks will reset whenever the server restarts. That is perfectly fine for learning, and we will cover the database upgrade later.

If you are still learning how the pieces of a web project fit together, read our guide on what a tech stack is first. It explains where a backend like this one sits in a full application.

Prerequisites Before You Build an Express API

You need only a few things to build and deploy an Express API. First, install Node.js on your computer. Second, create a free GitHub account. Third, pick a code editor, such as Visual Studio Code. Finally, make sure you can open a terminal on your system.

You also need a basic grasp of JavaScript. Variables, functions, and objects are enough. If arrow functions still feel strange, spend an evening on them before you continue.

Which Node.js and Express Versions Should You Use?

Version choices matter more than most tutorials admit. Express 5 is now the default version when you run npm install express. According to the official Express team, version 5.0.0 arrived in September 2024, and it became the default on npm in March 2025. Express 5 requires Node.js 18 or newer.

For Node.js itself, choose a Long-Term Support release. Node.js 24 is the Active LTS line as of this writing. Meanwhile, Node.js 26 enters LTS this month, so it is also a safe choice for new projects. You can check the current status on the official Node.js release schedule.

Confirm your setup by running these commands:

bash

node --version
npm --version
git --version

If all three print a version number, you are ready.

Minutes 0–5: Set Up Your Express API Project

The first five minutes cover the foundation. You will create a folder, initialize a project, and install four packages. Together, they give your Express API everything it needs for this tutorial.

Create the Folder and Install Dependencies

Open your terminal and run the following commands. They create a project folder and a package.json file.

bash

mkdir tasks-api
cd tasks-api
npm init -y
npm install express cors helmet express-rate-limit

Each package has a clear job. Express handles routing and requests. The cors package controls which websites may call your Express API from a browser. Helmet adds protective HTTP headers. Finally, express-rate-limit slows down abusive clients.

Configure package.json for Modern JavaScript

Next, open package.json and update it. We will use ES modules, which let you write import statements. We will also add two scripts.

json

{
  "name": "tasks-api",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "start": "node src/server.js",
    "dev": "node --watch --env-file=.env src/server.js"
  },
  "engines": {
    "node": ">=24"
  }
}

Keep the dependency list that npm already generated. Only add the fields shown here.

The dev script deserves a closer look. Node.js now includes a built-in watch mode and built-in support for .env files. Therefore, you no longer need nodemon or the dotenv package for a simple Express API. The engines field also tells hosting platforms which Node.js version to use.

Create Your Folder Structure

Now create the files you will fill in during the next steps:

bash

mkdir -p src/routes
touch src/app.js src/server.js src/routes/tasks.js .env .gitignore

Add these lines to .gitignore so secrets and bulky folders never reach GitHub:

node_modules
.env

In .env, add PORT=3000. With that, your project skeleton is ready, and the clock reads about five minutes.

Minutes 5–15: Build the Express API Routes

Now comes the main event. In this stage, you will write the code that makes your Express API useful. We will split the work into two files so the project stays tidy as it grows.

Why Separate app.js From server.js?

Splitting these files is a small habit with big benefits. The app.js file builds and configures the Express app. The server.js file only starts listening for traffic. Because of this separation, you can import the app into automated tests without opening a network port.

Start with src/server.js:

js

import app from "./app.js";

const PORT = process.env.PORT || 3000;

const server = app.listen(PORT, () => {
  console.log(`Express API listening on port ${PORT}`);
});

// Shut down cleanly when the host stops or redeploys the service
process.on("SIGTERM", () => {
  server.close(() => process.exit(0));
});

Notice that the code reads process.env.PORT. Hosting platforms assign a port at runtime, so hardcoding 3000 would break your deployment later.

Write the Tasks Router

Next, open src/routes/tasks.js. This file holds every route for the tasks resource. It stores data in a simple array and uses Node’s built-in randomUUID function to create IDs.

js

import { Router } from "express";
import { randomUUID } from "node:crypto";

const router = Router();

let tasks = [{ id: randomUUID(), title: "Learn Express", done: false }];

function isValidTitle(title) {
  return (
    typeof title === "string" &&
    title.trim().length > 0 &&
    title.length <= 200
  );
}

// GET /api/tasks  (optional filter: ?done=true)
router.get("/", (req, res) => {
  const { done } = req.query;
  if (done === "true" || done === "false") {
    return res.json(tasks.filter((t) => t.done === (done === "true")));
  }
  res.json(tasks);
});

// GET /api/tasks/:id
router.get("/:id", (req, res) => {
  const task = tasks.find((t) => t.id === req.params.id);
  if (!task) return res.status(404).json({ error: "Task not found" });
  res.json(task);
});

These first two routes read data. The list route even supports a simple filter through a query string. As a result, clients can request only finished tasks with /api/tasks?done=true.

Add the Create, Update and Delete Routes

Continue in the same file by adding the write operations:

js

// POST /api/tasks
router.post("/", (req, res) => {
  const { title } = req.body ?? {};
  if (!isValidTitle(title)) {
    return res
      .status(400)
      .json({ error: "A title of 1 to 200 characters is required" });
  }
  const task = { id: randomUUID(), title: title.trim(), done: false };
  tasks.push(task);
  res.status(201).json(task);
});

// PATCH /api/tasks/:id
router.patch("/:id", (req, res) => {
  const task = tasks.find((t) => t.id === req.params.id);
  if (!task) return res.status(404).json({ error: "Task not found" });

  const { title, done } = req.body ?? {};
  if (title !== undefined) {
    if (!isValidTitle(title)) {
      return res.status(400).json({ error: "Invalid title" });
    }
    task.title = title.trim();
  }
  if (done !== undefined) {
    if (typeof done !== "boolean") {
      return res.status(400).json({ error: "done must be true or false" });
    }
    task.done = done;
  }
  res.json(task);
});

// DELETE /api/tasks/:id
router.delete("/:id", (req, res) => {
  const index = tasks.findIndex((t) => t.id === req.params.id);
  if (index === -1) return res.status(404).json({ error: "Task not found" });
  tasks.splice(index, 1);
  res.status(204).end();
});

export default router;

The req.body ?? {} pattern is intentional. In Express 5, req.body is undefined when no body parser has run or no body arrives. Therefore, destructuring it directly would crash your Express API on an empty request.

Wire Everything Together in app.js

Now open src/app.js. This file connects the router, the middleware, and the error handlers. For now, we will add only the essentials, and then we will harden the app in the next stage.

js

import express from "express";
import cors from "cors";
import helmet from "helmet";
import tasksRouter from "./routes/tasks.js";

const app = express();

app.use(helmet());
app.use(cors());
app.use(express.json());

app.get("/", (req, res) => {
  res.json({ name: "Tasks API", status: "running" });
});

app.get("/health", (req, res) => {
  res.json({ status: "ok" });
});

app.use("/api/tasks", tasksRouter);

export default app;

The /health route is small but valuable. Hosting platforms can call it to check whether your Express API is alive. Meanwhile, express.json() is the built-in body parser that turns incoming JSON into req.body.

Run and Test Your Express API Locally

Start the server with npm run dev. Then open a second terminal and try these requests:

bash

curl http://localhost:3000/health

curl -X POST http://localhost:3000/api/tasks \
  -H "Content-Type: application/json" \
  -d '{"title":"Deploy my first Express API"}'

curl http://localhost:3000/api/tasks

If you see JSON in each response, congratulations. Your Express API works, and about fifteen minutes have passed. If you prefer a visual tool, Postman or the Thunder Client extension for VS Code can send the same requests.

Minutes 15–20: Add Validation, Error Handling and Security to Your Express API

A working Express API is not yet a safe one. Fortunately, five minutes is enough to cover the biggest risks. We will add friendly error responses, a catch-all route, and basic protection against abuse.

Handle 404s and Unexpected Errors

Right now, unknown URLs return a default HTML error page. That is awkward for an API, because clients expect JSON. Add the following to src/app.js, just above export default app;:

js

// Unknown routes
app.use((req, res) => {
  res.status(404).json({ error: "Route not found" });
});

// Central error handler (must have four parameters)
app.use((err, req, res, next) => {
  if (err.type === "entity.parse.failed") {
    return res.status(400).json({ error: "Invalid JSON in request body" });
  }
  console.error(err);
  res.status(500).json({ error: "Internal server error" });
});

Order matters here. Express runs middleware from top to bottom, so these handlers must come after every route. Additionally, the error handler must declare exactly four parameters, or Express will not treat it as an error handler.

Why Express 5 Makes Async Errors Easier

Here is a genuine improvement over older tutorials. In Express 5, a rejected promise inside an async route handler is passed to your error handler automatically. In Express 4, you needed a try/catch block or a wrapper library. Now this code is safe:

js

router.get("/example", async (req, res) => {
  const data = await someDatabaseCall(); // a thrown error reaches the handler above
  res.json(data);
});

Consequently, your Express API stays cleaner once you connect a real database.

Add Rate Limiting and Trust the Proxy

Next, protect your Express API from request floods. Add this import at the top of app.js:

js

import { rateLimit } from "express-rate-limit";

Then register the limiter before your routes, directly after express.json():

js

app.set("trust proxy", 1);

app.use(
  rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    limit: 100, // requests per IP in that window
  })
);

The trust proxy line is easy to forget. Hosting platforms place a proxy in front of your app. Without this setting, every visitor appears to share one IP address, and the limiter would block everyone together.

A Quick Word About Security Basics

Helmet already sets several protective headers, and CORS currently allows every origin. For a public demo, that is acceptable. For a real product, however, restrict CORS to your own front end by passing an origin option. In addition, review the OWASP API Security Project before you handle any real user data. It lists the most common mistakes that attackers exploit.

Minutes 20–23: Push Your Express API to GitHub

Your code now works locally, so it is time to prepare for deployment. Most modern hosts deploy straight from a Git repository. Therefore, your next job is to get the project onto GitHub.

Initialize Git and Commit

Run these commands from the project folder:

bash

git init
git add .
git commit -m "Build tasks Express API"
git branch -M main

Check that your .gitignore file works before moving on. Run git status and confirm that node_modules and .env are not listed. Pushing secrets to a public repository is one of the most common beginner mistakes. Attackers scan GitHub constantly, so a leaked key can be abused within minutes.

Create the Repository and Push

Go to GitHub and create a new empty repository named tasks-api. Do not add a README or a license, because your folder already has a history. Then copy the commands that GitHub displays, which look like this:

bash

git remote add origin https://github.com/YOUR-USERNAME/tasks-api.git
git push -u origin main

Refresh the repository page and confirm that your files appear. If Git still feels confusing, our roundup of Git tools for beginners shows visual apps that make commits and branches easier to understand.

Minutes 23–30: Deploy Your Express API on Render

Now for the exciting part. You will deploy your Express API so anyone can reach it. We will use Render, because its free web service needs no credit card and deploys directly from GitHub. If you want to compare other hosts first, read our guide on where to deploy your first Node.js app and our Render vs Railway comparison.

Create a Web Service

Follow these steps carefully:

  1. Sign up at Render with your GitHub account.
  2. Click New, then choose Web Service.
  3. Select your tasks-api repository.
  4. Set the language to Node.
  5. Enter npm install as the build command.
  6. Enter npm start as the start command.
  7. Choose the Free instance type.
  8. Click Deploy Web Service.

Render then streams the build log. Within a few minutes, you will see a message that your service is live. At that point, your Express API has a public address ending in onrender.com.

Pin the Node.js Version and Set the Health Check

Render’s native Node.js runtime currently defaults to a recent LTS release, and you can control it yourself. The engines field in package.json works, as does a .node-version file. The Render Node.js version guide explains which setting wins when you use several.

In the service settings, you can also set the health check path to /health. As a result, Render restarts your Express API automatically if it stops answering. If you prefer configuration as code, add a render.yaml file to your repository:

yaml

services:
  - type: web
    name: tasks-api
    runtime: node
    plan: free
    buildCommand: npm install
    startCommand: npm start
    healthCheckPath: /health

Test Your Live Express API

Copy the public URL from the Render dashboard. Then run the same requests you used locally, replacing the address:

bash

curl https://YOUR-SERVICE.onrender.com/health

curl -X POST https://YOUR-SERVICE.onrender.com/api/tasks \
  -H "Content-Type: application/json" \
  -d '{"title":"My live Express API works"}'

If both calls return JSON, you have finished. You were able to build and deploy an Express API in about half an hour. From now on, every push to your main branch triggers a fresh deployment automatically.

What to Expect From the Free Tier

Free hosting always comes with trade-offs, so set your expectations early. According to Render’s documentation, a free web service sleeps after 15 minutes without traffic. The next request then wakes it, and that wake-up takes about a minute. Free instances also use an ephemeral disk, so any file your app writes locally disappears after a restart.

For learning and portfolio demos, these limits are acceptable. However, if a recruiter will click your link, consider a paid always-on instance or a different host. The comparison in our Render vs Railway guide covers the real costs.

Express 5 Changes That Affect Your Express API

Many older tutorials still teach Express 4 patterns. Some of them break in Express 5, so a short checklist saves you debugging time. The official Express 5 migration guide covers every change, but these are the ones beginners hit most.

Route Path Syntax Is Stricter

Express 5 uses a newer version of the path-to-regexp library. As a result, a bare wildcard like * is no longer valid. You must give wildcards a name, such as /*splat. Similarly, several old regular-expression patterns and optional-parameter styles have changed. If a copied route throws a strange startup error, check its path first.

Async Handlers and Body Parsing

Two other changes help you directly. First, rejected promises now flow to your error handler, as you saw earlier. Second, req.body stays undefined until a parser fills it. That is why the code in this tutorial uses req.body ?? {}.

Removed and Renamed Methods

Express 5 also removed several deprecated methods. For example, res.json(status, obj) no longer works, so you should write res.status(status).json(obj) instead. Likewise, app.del() is gone, and you should use app.delete().

If you ever need to upgrade an older project, Express maintains an automated codemod. Still, the simplest path for a new Express API is to start on version 5 and avoid legacy snippets entirely.

Common Problems When You Build and Deploy an Express API

Even a careful first deployment can fail. Fortunately, most failures share a small set of causes. Check this list before you panic.

Build and Startup Failures

  • “Cannot find module” errors. Usually, a dependency is missing from package.json. Run npm install locally, commit the updated file, and push again.
  • ES module errors. If you see “Cannot use import statement outside a module,” confirm that "type": "module" exists in package.json.
  • Port problems. If the service never becomes healthy, make sure the code reads process.env.PORT instead of a fixed number.
  • Wrong start command. The start command must match your script, so npm start should run node src/server.js.

Runtime and Request Failures

  • CORS errors in the browser. Your Express API may be fine, while the browser blocks the call. Add the front end’s address to the origin option.
  • Empty request bodies. Check that the client sends a Content-Type: application/json header. Otherwise, express.json() ignores the body.
  • Missing data after a restart. Because tasks live in memory, a restart wipes them. This behavior is expected, and a database fixes it.
  • Slow first request. On a free plan, the service was probably asleep. Wait about a minute and try again.

When something fails, read the logs first. Render shows build and runtime logs in the dashboard, and the error message usually names the problem. If you get stuck, an AI assistant can help you interpret an error, provided you describe it well. Our guide on how to write better prompts for AI coding tools shows how to get useful answers, and our list of the best AI coding assistants for beginners can help you choose one.

Best Practices to Make Your Express API Production-Ready

A 30-minute Express API is a great start, but it is not the finish line. Before real users depend on your service, strengthen a few areas. The list below shows the highest-value upgrades.

Use a Real Database

First, replace the in-memory array with persistent storage. PostgreSQL is a strong default for most projects, and managed providers such as Neon offer free plans. Alternatively, MongoDB Atlas suits document-style data. Whichever you choose, store the connection string in an environment variable called DATABASE_URL, never in your code.

Validate Input With a Schema Library

Hand-written checks work for small APIs, but they get repetitive fast. Libraries such as Zod let you describe the shape of valid data once. Then they reject everything else with clear messages. This approach reduces bugs and keeps your Express API consistent.

Add Logging, Tests and Monitoring

Next, add request logging with a library like Morgan or Pino, so you can see what happens in production. In addition, write automated tests using Node’s built-in test runner. Since app.js exports the app without listening, tests can call your routes directly. Finally, connect an uptime monitor, so you learn about outages before your users do.

Version and Document Your Express API

Prefix routes with a version, such as /api/v1/tasks, so you can change behavior later without breaking existing clients. Furthermore, document your endpoints. A short README with example requests is enough at first. Over time, you can adopt the OpenAPI specification to generate interactive documentation.

Understand HTTP Status Codes

Good APIs use status codes consistently. For instance, 201 means created, 204 means success with no content, 400 means a bad request, and 404 means not found. The MDN HTTP status code reference explains every code in plain language. Using the right code makes your Express API easier for other developers to trust.

Add a Custom Domain and HTTPS to Your Express API

Your onrender.com address works, but a custom domain looks more professional. Better still, it makes your Express API easier to remember. The process has two parts: you buy or own a domain, and you point it at your host.

How Custom Domains Work

Render lets you add a custom domain from the service settings. After that, it shows the DNS records you must create at your domain provider. If DNS is new to you, our explainer on how DNS works clarifies what those records do. Moreover, if you manage your domain on Cloudflare, follow our walkthrough on how to connect a Cloudflare domain to your web host.

Why HTTPS Is Not Optional

Render issues TLS certificates automatically, so your Express API is served over HTTPS from the first minute. That protects data in transit, including passwords and tokens. If you want to understand what happens behind the padlock, read our beginner-friendly guide to HTTPS and SSL certificates. In short, never expose an API that handles personal data over plain HTTP.

Where to Go Next After Your First Express API

You have now completed the full cycle: setup, routes, safety, version control, and deployment. Next, choose one upgrade and ship it this week. Doing so cements the skills faster than reading more tutorials.

Here are three strong follow-up projects:

  1. Add authentication. Protect your Express API with JSON Web Tokens or a session library, so each user sees only their own tasks.
  2. Connect a front end. Build a small React or plain JavaScript page that calls your live endpoints.
  3. Swap in a database. Move your tasks into Postgres and keep your data after restarts.

Each project teaches a different layer of modern web development. Together, they turn a tutorial exercise into a portfolio piece.

Frequently Asked Questions About Building an Express API

How long does it take to build and deploy an Express API?

A small Express API takes about 30 minutes if you follow a clear guide. Setup and routes take roughly 15 minutes. Security, GitHub, and deployment take the rest. Larger projects with databases and authentication naturally take longer.

Is Express still worth learning in 2026?

Yes. Express remains one of the most widely used Node.js frameworks, and version 5 is now the default on npm. It is simple, well documented, and supported by a huge ecosystem. Consequently, it is an excellent first backend framework, even though newer alternatives exist.

What is the difference between Express and Node.js?

Node.js is a JavaScript runtime that executes code outside the browser. Express is a framework that runs on top of Node.js. It adds routing, middleware, and helpers that make building an Express API much faster than using Node’s raw HTTP module.

Can I deploy an Express API for free?

Yes. Render offers a free web service that deploys from GitHub without a credit card. However, free services sleep after inactivity, so the first request can take about a minute. Our Render vs Railway guide compares the free options in detail.

Why does my Express API work locally but fail after deployment?

The cause is usually a hardcoded port, a missing dependency, or a missing environment variable. Read the build and runtime logs, because they typically name the exact problem. Also confirm that your start command matches your package.json script.

Should I use Express or a newer framework such as Fastify?

For learning, Express is hard to beat because tutorials, answers, and packages are everywhere. Fastify and similar frameworks offer speed or built-in validation. Even so, the concepts you learn with Express, such as routing and middleware, transfer directly to them.

Conclusion: You Can Build and Deploy an Express API Today

You have just learned how to build and deploy an Express API from an empty folder to a live URL. First, you set up a modern Node.js project. Next, you created five REST routes and added validation, error handling, and security middleware. Then you pushed the code to GitHub and published it on Render.

The most important lesson is that deployment is not scary. With a clean structure, a few environment-aware habits, and a Git-based workflow, shipping becomes routine. Moreover, every project you build from here uses the same pattern.

So open your terminal and start the timer. Build your own Express API, deploy it, and share the link with a friend. Then pick one upgrade from the list above and keep going. The sooner you ship, the faster you will learn.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *