By Backend BasicsJuly 16, 2026·9 min read

What Is a Server? The Client-Server Model Explained

A server is a program that waits for requests and answers them. How clients find it, what happens inside the process, client-server architecture, and physical vs serverless.

A server is a program that waits for someone to ask it for something, then answers. Here's how a server works, in five steps: it listens on a network port, accepts a connection when a client arrives, reads and parses the request, runs code to decide the answer, and writes a response back. Routes, databases, caches and queues, everything else in backend development, are different answers to one question: how does a server handle a request well? Get this picture straight first and every tool later in this series has somewhere to fit.

The client-server model

A server is a program (running on a computer) whose entire job is to wait for requests and respond to them. A client is anything that sends those requests: a browser, a mobile app, another server. The relationship is always the same shape: the client asks, the server answers. Nothing happens until a client initiates it; a server doesn't push work onto you uninvited, it reacts. (WebSockets, Server-Sent Events and push notifications bend this rule, but even they start with the client connecting first.)

A useful way to picture it: you're at a restaurant. You (the client) tell a waiter what you want (the request). The kitchen (the server) has the ingredients and the staff to prepare it, and eventually sends the finished plate back to your table (the response). If the kitchen is closed or out of an ingredient, you get told so. That's exactly what an HTTP error status like 404 or 500 is: the server's way of saying "I couldn't fulfill that."

a server, in code
// This is a complete, working server. That's really all a server is:
// a program that waits, and responds when asked.
const http = require("http");

const server = http.createServer((request, response) => {
  response.writeHead(200, { "Content-Type": "text/plain" });
  response.end("Hello from the server!");
});

server.listen(3000, () => {
  console.log("Listening on port 3000");
});

What makes something "a server": hardware

Physically, a server is just a computer built to run unattended for years, usually racked in a data center with no monitor or keyboard. You can turn your laptop into one for learning (and you will), but production servers are built for thousands of simultaneous users, near-continuous uptime and fast, stable connections.

What makes something "a server": software

Hardware alone is just a box. What turns it into a specific kind of server is the software running on it. Install web server software and it becomes a web server. Install mail server software and it becomes a mail server. The same physical machine can even run several server programs at once, each doing a different job on a different port. Think of hardware as a body and software as a profession. The body doesn't determine whether someone is a doctor or a pilot; their training does.

How a client actually finds a server

Every server has an IP address: a numeric address on the network, like a house address. Since people can't easily remember IP addresses, we use domain names (like example.com) instead, and DNS (the Domain Name System) is the phonebook that translates a domain name into the IP address a browser can actually connect to. Once connected to the right machine, the client also needs to reach the right program on it. That's what a port number does. Port 443 conventionally means "talk to whatever's listening for HTTPS traffic," port 5432 conventionally means Postgres, and so on. An IP address gets you to the building; a port number gets you to the right room.

raw HTTP request/response
GET / HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0
Accept: text/html

--- server responds ---

HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 1256

<!DOCTYPE html>
<html>...</html>
checking DNS and connecting manually
# Resolve a domain to an IP
dig example.com +short
# prints an IP address, e.g. 203.0.113.10 (your output will differ)

# Talk to a server directly, bypassing the browser
curl -v https://example.com

# See which process is using port 3000
lsof -i :3000

Sequence: the browser asks a DNS resolver for example.com's IP address and gets 203.0.113.10. It opens a TCP and TLS connection to the server on port 443, sends HTTP GET /, the server processes the request and replies 200 OK with HTML or JSON, and the browser renders it.

What happens when you open a website

That whole exchange (look up the address, connect, ask, wait, receive) happens in a fraction of a second, on nearly every request you'll ever make, whether you're loading a webpage or your NestJS app is calling another API.

How does a server work? Inside the process

The diagram above is the trip across the network. Here's what happens inside the server program when the request arrives:

  1. Listen. At startup the program asks the operating system for a port, say 3000. From then on, the operating system hands it every connection that arrives on that port. That's all "listening on port 3000" means.
  2. Accept. A client connects. The operating system completes the TCP handshake, and the server accepts the connection and gets a socket: a two-way pipe to that one client.
  3. Parse. The server reads bytes from the socket and turns them into a request: the method (GET, POST), the path (/users/42), the headers and an optional body.
  4. Handle. The server picks the code that matches the method and path, called a route handler, and runs it. This is where your code queries the database, checks a cache or calls another API.
  5. Respond. The server writes a status line (200 OK, 404 Not Found), headers and a body back down the socket, then either closes the connection or keeps it open for the next request.

Here are the same five steps without the http module, using Node's lower-level net module so nothing is hidden. Save it as server.js, run node server.js, and try curl -i localhost:3000 in another terminal:

server.js: the five steps by hand
const net = require("node:net");

// 1. Listen: ask the operating system for port 3000.
const server = net.createServer((socket) => {
  // 2. Accept: a client connected, and Node hands us a socket for it.
  socket.once("data", (chunk) => {
    // 3. Parse: the first line of an HTTP request is "METHOD PATH VERSION".
    // (A real server keeps reading until the headers end; this demo
    // assumes the whole request arrived in one chunk.)
    const requestLine = chunk.toString().split("\r\n")[0];
    const [method, path] = requestLine.split(" ");
    console.log(`${method} ${path}`);

    // 4. Handle: decide what the answer is.
    const found = method === "GET" && path === "/";
    const status = found ? "200 OK" : "404 Not Found";
    const body = found ? "Hello from the server!" : "Not found";

    // 5. Respond: status line, headers, a blank line, then the body.
    socket.end(
      `HTTP/1.1 ${status}\r\n` +
        "Content-Type: text/plain\r\n" +
        `Content-Length: ${Buffer.byteLength(body)}\r\n` +
        "Connection: close\r\n" +
        "\r\n" +
        body
    );
  });
});

server.listen(3000, () => {
  console.log("Listening on port 3000");
});

In real projects you never write this part. Node's http module, and frameworks built on it like NestJS, do the listening, accepting and parsing for you, including everything this demo skips: requests that arrive in several chunks, keep-alive connections and malformed input. What you write is step 4, the handler. In NestJS that's your controllers and services. For the message format itself, MDN's overview of HTTP is the best short reference.

Handling many requests at once: event loop vs threads

A busy server has thousands of these conversations going at the same time. There are two main ways to handle that:

  • –A thread (or process) per connection. Each connection gets its own thread, which runs the handler from start to finish and simply waits while the database answers. Classic Apache setups and many Java servers work this way. It's easy to reason about, but every thread costs memory, so how many connections you can serve at once is capped by how many threads you can afford.
  • –An event loop. One thread handles many connections and never sits waiting. When a handler asks the database for data, that request is parked, the thread moves on to the next one, and it comes back when the answer arrives. Node.js (one main thread) and Nginx (a few worker processes, each with its own event loop) work this way. It handles huge numbers of mostly idle connections cheaply, with one catch: if a handler does heavy CPU work, like a long loop or hashing a big file, every other request waits behind it.

That catch is why Node's own guide is called Don't Block the Event Loop, and why slow work like sending emails, resizing images or building reports usually moves out of the request and into a background job queue. Real systems mix the two models anyway: Node runs some file, DNS and crypto work on a small internal thread pool, and you scale out by running several copies of the server behind a load balancer.

Different kinds of servers you'll run into

  • –Web server: serves HTML, CSS, JS, or API responses over HTTP (e.g. Nginx, or your NestJS app itself).
  • –Application server: runs your actual business logic. In most modern stacks this is the same process as the web server (this is literally what NestJS is).
  • –Database server: stores and retrieves structured data (PostgreSQL, MySQL).
  • –Cache server: keeps hot data in memory for fast repeated reads (Redis).
  • –Mail server: sends and receives email (SMTP, IMAP).

A typical backend project is really several of these servers working together: your NestJS app is the web/application server, talking to a PostgreSQL database server, a Redis cache server, and maybe an Elasticsearch server for search, all over the network, all using this exact same request-response pattern underneath.

A browser connects over HTTPS on port 443 to a NestJS app server, which connects to PostgreSQL on port 5432, Redis on port 6379 and Elasticsearch on port 9200.

One app server talking to other servers on their ports

Client-server architecture: two-tier, three-tier and n-tier

The client-server model is the ask-and-answer relationship. Client-server architecture is how you arrange the pieces:

  • –Two-tier: the client talks straight to the database server. Older desktop apps that connected directly to a shared database worked like this. It's simple, but every client needs database credentials, and business rules get copied into every client.
  • –Three-tier: presentation (the browser or mobile app), application (your API server) and data (the database). The client only ever talks to the API, and the API is the only thing allowed to touch the database. This is the default shape of a modern web app.
  • –N-tier: three-tier plus more specialised servers, like a Redis cache, a search engine and a job queue. The diagram above is an n-tier setup. Each extra tier is another server on its own port, using the same request-response pattern.

The alternative is peer-to-peer, where every machine is both client and server and talks directly to the others, as in BitTorrent. It removes the central server, but you also lose the one place to enforce rules, keep the true copy of the data and ship fixes, which is why most web apps are client-server.

Physical, virtual and serverless servers

"Server" also gets used for where the program runs. Your code is the same request handler in all four cases. What changes is who manages the machine underneath.

  • –Physical (bare metal): a real machine you own or rent whole. Full control and no neighbours, but hardware failures are your problem, and adding capacity means getting another box.
  • –Virtual machine: software called a hypervisor splits one physical machine into several isolated virtual ones, each with its own operating system. A cloud VM, like an AWS EC2 instance, is one of these. You can create or resize one in minutes.
  • –Container: a lighter slice of a machine that shares the host's operating system kernel but has its own files and processes. Docker is the common tool. Containers start in seconds and are a common way to package and deploy apps onto VMs.
  • –Serverless: you hand the platform only your handler function (AWS Lambda, Cloudflare Workers, Vercel Functions). The platform does the listening and accepting, starts copies of your function as requests arrive, removes them when traffic drops, and bills you for what runs. The trade-offs: a delay on some platforms when a new copy has to start (a cold start), a time limit per invocation, and no guarantee that anything in memory survives to the next request, so state lives in a database or cache.

Common questions

What is the difference between a client and a server?

The client starts the conversation by sending a request. The server waits for requests and answers them. One program can be both: your NestJS app is a server to the browser and a client to PostgreSQL.

What does "listening on a port" mean?

The program has asked the operating system for every connection that arrives on that port number. Normally only one program can listen on a given port at a time, which is why starting a second copy fails with "address already in use" (EADDRINUSE in Node).

What happens when a server gets too many requests?

New connections queue up, responses slow down, and eventually requests time out or fail with errors like 503 Service Unavailable. The usual fixes are caching, moving slow work into background jobs, and running more copies of the server behind a load balancer.

Is a web server the same as an application server?

Not always. A web server like Nginx is good at serving files and passing requests along, and an application server runs your business logic. In many Node setups they're the same process, often with Nginx or a cloud load balancer in front.

Once "server" stops meaning a mysterious box and starts meaning "a program that answers requests," backend work turns into smaller questions: which server should answer, how fast, and what it needs to remember between requests. The rest of this series is just answering those questions one tool at a time, starting with how NestJS, PostgreSQL, Redis, BullMQ and Elasticsearch fit together. I'm a frontend engineer who builds the UI on top of servers like these. If you need that, see my work or get in touch.