Skip to content
Express.js ex getting-started 4 min read

Your First Express App

The fastest way to understand Express is to build the smallest possible working server: create an app, define one route, and start it listening on a port. In a dozen lines you have a real HTTP server you can hit from a browser or curl. This page walks through the canonical app.js, explains every line, and shows how to run it with Node — the foundation that every larger Express application grows from.

The complete Hello World app

Create a file named app.js in an empty project folder. (If you have not installed Express yet, see Installation and setup first — you need express in your node_modules.)

const express = require("express");

const app = express();
const PORT = 3000;

app.get("/", (req, res) => {
  res.send("Hello, World!");
});

app.listen(PORT, () => {
  console.log(`Server running at http://localhost:${PORT}`);
});

That is a fully functional web server. Run it and visit http://localhost:3000 in your browser, and you will see Hello, World! rendered as plain text.

Running the app

Start the server from your terminal with the Node runtime:

node app.js

Output:

Server running at http://localhost:3000

The process stays alive and keeps listening — it will not return to your shell prompt until you stop it with Ctrl+C. While it runs, send it a request from another terminal:

curl http://localhost:3000

Output:

Hello, World!

Tip: Running node app.js does not reload when you edit the file. During development, use a watcher so changes restart the server automatically — node --watch app.js (Node 18+) or a tool like nodemon.

Line by line

Each line in app.js plays a specific role. Understanding them now makes every later Express concept easier.

Importing Express

const express = require("express");

This loads the Express module. express is a function — calling it creates a new application instance. (If your project uses ES modules, with "type": "module" in package.json, write import express from "express"; instead.)

Creating the application

const app = express();

app is your application object. It holds your routes and middleware and exposes the methods you will use throughout the app: app.get(), app.post(), app.use(), app.listen(), and more. Almost everything you do in Express happens through this object.

Defining a route

app.get("/", (req, res) => {
  res.send("Hello, World!");
});

This registers a route handler. It tells Express: when a GET request arrives for the path /, run this function. The handler receives two key arguments:

ArgumentStands forWhat it is
reqrequestThe incoming request — URL, headers, query string, body, params
resresponseThe response object you use to send data back to the client

Here res.send() sends the response. Express inspects what you pass it and sets sensible headers automatically: a string becomes text/html, while an object or array is serialized to JSON. The function returns nothing — sending the response is the result.

Starting the server

app.listen(PORT, () => {
  console.log(`Server running at http://localhost:${PORT}`);
});

app.listen() binds the app to a TCP port and begins accepting connections. The optional callback fires once the server is ready, which is the right place to log a startup message. Until you call listen, your routes are defined but nothing is actually serving traffic.

Returning JSON instead of text

Most real Express apps serve JSON APIs rather than plain text. Swapping res.send() for res.json() is all it takes — Express sets the Content-Type: application/json header and serializes the value for you.

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

A GET /api/health request now returns:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"status":"ok","uptime":12.84}

You can also use an async handler when your route needs to await a database call or another service:

app.get("/api/time", async (req, res) => {
  const now = await Promise.resolve(new Date().toISOString());
  res.json({ now });
});

Note: In Express 5, if an async handler rejects, the error is forwarded to your error-handling middleware automatically. In Express 4 you must try/catch and call next(err) yourself. See Express 4 vs 5 for the full list of differences.

Setting the port from the environment

Hardcoding the port is fine for a first app, but production environments (and platforms like Heroku, Render, or Docker) often inject the port through an environment variable. Read it with a sensible fallback:

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

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

Now the same code runs locally on 3000 and adapts to whatever port the host assigns.

Best Practices

  • Keep your entry file (app.js or server.js) small — its job is to create the app, wire up middleware, and call listen.
  • Read the port from process.env.PORT with a local fallback so the app is portable across environments.
  • Use res.json() for API responses and res.send() for simple text or HTML — let Express set the headers for you.
  • Log a clear startup message in the listen callback so you can confirm the server is ready and on which port.
  • Use node --watch or nodemon in development so you do not have to restart the server by hand after every edit.
  • As soon as you add a second route, start thinking about structure — split routes into routers and move logic out of handlers.
Last updated June 14, 2026
Was this helpful?