---
title: "Basic Server"
order: 3
---

In the previous chapter, you sent requests from a client to a test server. Now let's walk through how that server actually works.

## Starting the Server

Once you've registered your routes, call `svr.listen()` to start the server.

```cpp
svr.listen("0.0.0.0", 8080);
```

The first argument is the host, and the second is the port. `"0.0.0.0"` listens on all network interfaces. Use `"127.0.0.1"` if you want to accept connections from your own machine only.

`listen()` is a blocking call. It won't return until the server stops. The server keeps running until you press `Ctrl+C` in your terminal or call `svr.stop()` from another thread.

## Routing

Routing is the heart of any server. It's how you tell cpp-httplib: when a request comes in for this URL with this HTTP method, run this code.

```cpp
httplib::Server svr;

svr.Get("/hi", [](const httplib::Request &req, httplib::Response &res) {
    res.set_content("Hello!", "text/plain");
});
```

`svr.Get()` registers a handler for GET requests. The first argument is the path, the second is the handler function. When a GET request arrives at `/hi`, your lambda runs.

There's a method for each HTTP verb.

```cpp
svr.Get("/path",    handler);  // GET
svr.Post("/path",   handler);  // POST
svr.Put("/path",    handler);  // PUT
svr.Delete("/path", handler);  // DELETE
```

The handler signature is `(const httplib::Request &req, httplib::Response &res)`. You can use `auto` to keep it short.

```cpp
svr.Get("/hi", [](const auto &req, auto &res) {
    res.set_content("Hello!", "text/plain");
});
```

The handler only runs when the path matches. Requests to unregistered paths automatically return 404.

## The Request Object

The first parameter `req` gives you everything the client sent.

### Body

`req.body` holds the request body as a `std::string`.

```cpp
svr.Post("/post", [](const auto &req, auto &res) {
    // Echo the body back to the client
    res.set_content(req.body, "text/plain");
});
```

### Headers

Use `req.get_header_value()` to read a request header.

```cpp
svr.Get("/check", [](const auto &req, auto &res) {
    auto auth = req.get_header_value("Authorization");
    res.set_content("Auth: " + auth, "text/plain");
});
```

### Query Parameters and Form Data

`req.get_param_value()` retrieves a parameter by name. It works for both GET query parameters and POST form data.

```cpp
svr.Get("/search", [](const auto &req, auto &res) {
    auto q = req.get_param_value("q");
    res.set_content("Query: " + q, "text/plain");
});
```

A request to `/search?q=cpp-httplib` gives you `"cpp-httplib"` for `q`.

To loop over all parameters, use `req.params`.

```cpp
svr.Post("/submit", [](const auto &req, auto &res) {
    std::string result;
    for (auto &[key, val] : req.params) {
        result += key + " = " + val + "\n";
    }
    res.set_content(result, "text/plain");
});
```

### File Uploads

Files uploaded via multipart form data are available through `req.form.get_file()`.

```cpp
svr.Post("/upload", [](const auto &req, auto &res) {
    auto f = req.form.get_file("file");
    auto content = f.filename + " (" + std::to_string(f.content.size()) + " bytes)";
    res.set_content(content, "text/plain");
});
```

`f.filename` gives you the filename, and `f.content` gives you the file data.

## Path Parameters

Sometimes you want to capture part of the URL as a variable -- for example, the `42` in `/users/42`. Use the `:param` syntax to do that.

```cpp
svr.Get("/users/:id", [](const auto &req, auto &res) {
    auto id = req.path_params.at("id");
    res.set_content("User ID: " + id, "text/plain");
});
```

A request to `/users/42` gives you `"42"` from `req.path_params.at("id")`. `/users/100` gives you `"100"`.

You can capture multiple segments at once.

```cpp
svr.Get("/users/:user_id/posts/:post_id", [](const auto &req, auto &res) {
    auto user_id = req.path_params.at("user_id");
    auto post_id = req.path_params.at("post_id");
    res.set_content("User: " + user_id + ", Post: " + post_id, "text/plain");
});
```

### Regex Patterns

You can also write a regular expression directly in the path instead of `:param`. Capture group values are available via `req.matches`, which is a `std::smatch`.

```cpp
// Only accept numeric IDs
svr.Get(R"(/files/(\d+))", [](const auto &req, auto &res) {
    auto id = req.matches[1];  // First capture group
    res.set_content("File ID: " + std::string(id), "text/plain");
});
```

`/files/42` matches, but `/files/abc` doesn't. This is handy when you want to constrain what values are accepted.

## Building a Response

The second parameter `res` is how you send data back to the client.

### Body and Content-Type

`res.set_content()` sets the body and Content-Type. That's all you need for a 200 response.

```cpp
svr.Get("/hi", [](const auto &req, auto &res) {
    res.set_content("Hello!", "text/plain");
});
```

### Status Code

To return a different status code, assign to `res.status`.

```cpp
svr.Get("/not-found", [](const auto &req, auto &res) {
    res.status = 404;
    res.set_content("Not found", "text/plain");
});
```

### Response Headers

Add response headers with `res.set_header()`.

```cpp
svr.Get("/with-header", [](const auto &req, auto &res) {
    res.set_header("X-Custom", "my-value");
    res.set_content("Hello!", "text/plain");
});
```

## Walking Through the Test Server

Now let's use what we've learned to read through the test server from the previous chapter.

### GET /hi

```cpp
svr.Get("/hi", [](const auto &, auto &res) {
    res.set_content("Hello!", "text/plain");
});
```

The simplest possible handler. We don't need any information from the request, so the `req` parameter is left unnamed. It just returns `"Hello!"`.

### GET /search

```cpp
svr.Get("/search", [](const auto &req, auto &res) {
    auto q = req.get_param_value("q");
    res.set_content("Query: " + q, "text/plain");
});
```

`req.get_param_value("q")` pulls out the query parameter `q`. A request to `/search?q=cpp-httplib` returns `"Query: cpp-httplib"`.

### POST /post

```cpp
svr.Post("/post", [](const auto &req, auto &res) {
    res.set_content(req.body, "text/plain");
});
```

An echo server. Whatever body the client sends, `req.body` holds it, and we send it straight back.

### POST /submit

```cpp
svr.Post("/submit", [](const auto &req, auto &res) {
    std::string result;
    for (auto &[key, val] : req.params) {
        result += key + " = " + val + "\n";
    }
    res.set_content(result, "text/plain");
});
```

Loops over the form data in `req.params` using structured bindings (`auto &[key, val]`) to unpack each key-value pair.

### POST /upload

```cpp
svr.Post("/upload", [](const auto &req, auto &res) {
    auto f = req.form.get_file("file");
    auto content = f.filename + " (" + std::to_string(f.content.size()) + " bytes)";
    res.set_content(content, "text/plain");
});
```

Receives a file uploaded via multipart form data. `req.form.get_file("file")` fetches the field named `"file"`, and we respond with the filename and size.

### GET /users/:id

```cpp
svr.Get("/users/:id", [](const auto &req, auto &res) {
    auto id = req.path_params.at("id");
    res.set_content("User ID: " + id, "text/plain");
});
```

`:id` is the path parameter. `req.path_params.at("id")` retrieves its value. `/users/42` gives you `"42"`, `/users/alice` gives you `"alice"`.

### GET /files/(\d+)

```cpp
svr.Get(R"(/files/(\d+))", [](const auto &req, auto &res) {
    auto id = req.matches[1];
    res.set_content("File ID: " + std::string(id), "text/plain");
});
```

The regex `(\d+)` matches numeric IDs only. `/files/42` hits this handler, but `/files/abc` returns 404. `req.matches[1]` retrieves the first capture group.

## Next Steps

You now have the full picture of how a server works. Routing, reading requests, building responses -- that's enough to build a real API server.

Next, let's look at serving static files. We'll build a server that delivers HTML and CSS.

**Next:** [Static File Server](../04-static-file-server)
