---
title: C++ on the Compute platform
summary: null
url: https://www.fastly.com/documentation/guides/compute/developer-guides/cpp
---

The Compute platform supports application code written in [C++](https://isocpp.org/), a language for high-performance applications.

## Quick access

- [SDK reference](https://cpp-compute-sdk.fastly.dev/)
- [Code examples](/solutions/examples/cpp/)
- [Starter kit](/solutions/starters/compute-starter-kit-cpp-default/)

## C++ language support

The C++ SDK for Compute uses the [WASI C++ toolchain](https://component-model.bytecodealliance.org/language-support/building-a-simple-component/c.html) to build C++ applications to Wasm binaries.

In order to develop for Fastly Compute in C++, obtain the following tools for your platform:

- [CMake 3.x](https://cmake.org/download/#legacy) 3.25 or newer, not compatible with CMake 4
- [GNU make](https://www.gnu.org/software/make/)
- [WASI SDK](https://github.com/webassembly/wasi-sdk/releases) 25.0 or newer
- [Fastly CLI](https://www.fastly.com/documentation/reference/tools/cli/) 14.0 or newer

This document assumes that `cmake`, `make`, and `fastly` are available on the system path, and that the WASI SDK is available at `/opt/wasi-sdk`.

> **HINT:** For best results, expand the SDK to `/opt/` and create a symbolic link (e.g., `/opt/wasi-sdk`) to maintain a consistent path across version updates.
>
> **Example (macOS Apple Silicon):**
>
> ```term
> sudo tar -xzf ./wasi-sdk-32.0-arm64-macos.tar.gz -C /opt
> sudo ln -sfn /opt/wasi-sdk-32.0-arm64-macos /opt/wasi-sdk
> # On macOS, remove the provenance attribute to allow the compiler to run:
> xattr -rd com.apple.quarantine /opt/wasi-sdk
> ```

For running unit tests, also obtain [Viceroy](https://github.com/fastly/Viceroy/tree/main) (0.15.0 or newer) and place it on the system path.

### Verify your environment

Before proceeding, ensure your tools are responsive:

```term
cmake --version                # Should be 3.25 or newer
head -1 /opt/wasi-sdk/VERSION  # Should be 25 or newer
fastly version                 # Should be 14.0 or newer

# For unit testing
viceroy --version              # Should be 0.15.0 or newer
```

## Project layout

If you don't yet have a working toolchain and Compute service set up, start by [getting set up](https://www.fastly.com/documentation/guides/compute/getting-started-with-compute/).

A new project initialized from the [default starter kit](https://github.com/fastly/compute-starter-kit-cpp-default) will contain a simple file tree:

```plain lineNumbers=false nocopy
├── .fastlyignore
├── .gitignore
├── CMakeLists.txt
├── CMakePresets.json
├── README.md
├── fastly.toml
└── src
    └── main.cpp
```

### Building and running the application

To turn your source code into a running service, you first configure the build system and then start the local testing server.

#### Configuration

The recommended way to manage the build process is to use [CMake presets](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html). The starter kit includes a `CMakePresets.json` file that defines the `wasi` preset, which has been set up to use the WASI-SDK toolchain.

```json context="📄 CMakePresets.json - \\"wasi\\" preset"
{
  "version": 3,
  "configurePresets": [
    {
      "name": "wasi",
      "generator": "Unix Makefiles",
      "binaryDir": "${sourceDir}/build",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE": "Debug",
        "CMAKE_TOOLCHAIN_FILE": "/opt/wasi-sdk/share/cmake/wasi-sdk-p1.cmake"
      }
    }
  ]
}
```

Configure the project by running `cmake` against this preset.

```term
cmake --preset wasi
```

#### The build-and-serve Loop

The easiest way to build and test your application is using the Fastly CLI.

```term
fastly compute serve
```

When you run the above command, the CLI looks at the `[scripts.build]` value in your `fastly.toml` file, and runs this build script automatically before starting the local server:

```toml context="📄 fastly.toml - \\"scripts.build\\" script"
[scripts]
  build = "cmake --build build && cp build/main.wasm bin/main.wasm"
```

Once the server is running (usually on `http://127.0.0.1:7676`), you can test it by accessing it via a web browser, or by using `curl` in a separate terminal:

```term
curl -i http://127.0.0.1:7676/
```

If you are running the default starter kit, this request will return a HTML response.

### Key files

- `src/main.cpp`: The entry point of your application, which contains the logic you'll run on incoming requests.
- `CMakeLists.txt`: Describes your dependencies and build steps.
  - If you add more source files (like `src/utils.cpp`), you must add them to the [add_executable()](https://cmake.org/cmake/help/v3.31/command/add_executable.html) command in this file so the compiler knows to include them.
- `CMakePresets.json`: Provides configuration presets (e.g., `wasi` for production, `wasi-testing` for testing).
- `fastly.toml`: Contains metadata required by Fastly to deploy your package. [Learn more about `fastly.toml`](https://www.fastly.com/documentation/reference/compute/fastly-toml/).

### The Fastly dependency

The Fastly C++ SDK is added to the application using the [FetchContent](https://cmake.org/cmake/help/v3.31/module/FetchContent.html) module in `CMakeLists.txt`. This automatically downloads the SDK at configuration time:

```cmake context="📄 CMakeLists.txt - Adding the Fastly SDK"
include(FetchContent)
FetchContent_Declare(
        fastly_sdk
        URL https://github.com/fastly/compute-sdk-cpp/releases/download/v0.6.0/fastly-cpp-v0.6.0.tar.gz
        DOWNLOAD_EXTRACT_TIMESTAMP NEW
        SYSTEM
)
FetchContent_MakeAvailable(fastly_sdk)
find_package(fastly REQUIRED PATHS ${fastly_sdk_SOURCE_DIR} NO_DEFAULT_PATH)
```

The `SYSTEM` keyword enables the compiler to treat the SDK as a third-party library. Once configured, you can include the SDK in your source:

```cpp
#include <fastly/sdk.h>
```

## Main interface

Start a Compute program by defining a `main()` function:

```cpp context="📄 src/main.cpp - Minimal application"
#include <fastly/sdk.h>

int main() {
    auto req = fastly::Request::from_client();

    // Simple synthetic response
    auto res = fastly::Response::from_status(200)
        .with_body("Hello from the edge!");

    res.send_to_client();
    return 0;
}
```

The [Fastly Compute C++ SDK](https://cpp-compute-sdk.fastly.dev) provides the core object types such as `Request`, `Response`, and `Body` referenced by your application.

The program will be invoked for each request that Fastly receives for a domain attached to your service, and it is expected to send a response that can be served to the client.

## The `Request` and `Response` objects

The Fastly C++ SDK uses the `fastly::Request` and `fastly::Response` classes to model HTTP traffic. These classes provide a structured way to capture, modify, and route data between your users and your backends.

### Working with requests

An application typically interacts with requests in one of two ways:

- **Downstream (from the client):** Use `fastly::Request::from_client()` to access the request sent by the user to the Fastly edge.
- **Synthetic:** Use `fastly::Request` constructors to manually build a new request from scratch.

Once an object is instantiated, you can inspect or manipulate it using built-in methods such as:

- **Examine:** Retrieve metadata using `get_headers()`, `get_method()`, or `get_url()`.
- **Modify:** Change behavior using `set_ttl()`, `set_pass()`, or by adding headers directly to the request with `req.get_headers().insert(...)`.

### Communicating with backend servers and the Fastly cache

A `fastly::Request` can be forwarded to any upstream [backend](https://www.fastly.com/documentation/guides/integrations/non-fastly-services/developer-guide-backends) defined on your service.

To retrieve content from an origin, you "send" a request to a defined backend.

```cpp context="📄 src/main.cpp - Sending a request to a named backend"
constexpr auto backend_name = "my_backend_name";
auto res = req.send(backend_name);
```

> **HINT:** It's a good idea to define backend names as constants.

Requests forwarded to a backend will typically transit the Fastly readthrough cache interface, and the response may come from cache. See [Readthrough (HTTP) cache](https://www.fastly.com/documentation/guides/concepts/cache#readthrough-cache) for more precise or explicit control over the Fastly readthrough cache.

Additionally, the SDK supports [dynamic backends](https://www.fastly.com/documentation/guides/integrations/non-fastly-services/developer-guide-backends#dynamic-backends) created at runtime using [`fastly::backend::BackendBuilder`](https://cpp-compute-sdk.fastly.dev/classfastly_1_1backend_1_1BackendBuilder.html).

#### Handling failures

Because network operations can fail, the `.send()` method returns a `fastly::expected` container.

```cpp context="📄 src/main.cpp - Handling failures"
#include <fastly/sdk.h>

constexpr auto backend_name = "my_backend_name";

int main() {
    auto req = fastly::Request::from_client();
    // section visible
    auto result = req.send(backend_name);

    if (!result) {
        // Error path: Generate a local synthetic response
        fastly::Response{}.with_status(502).send_to_client();
        return 1;
    }

    // Success path: Send the backend response as-is
    result->send_to_client();  // This is equivalent to result.value().send_to_client();
    // section-end visible
}
```

> **IMPORTANT:** This is a common pattern in this SDK. Rather than utilizing traditional C++ exceptions, return values from such operations favor a safer and more explicit check, forcing a clean branching path in your code. A `fastly::expected<T>` will either contain a valid **T** or a **FastlyError** object.
>
> To keep examples concise, this documentation assumes that calls succeeded. In a production environment, you should always check the `fastly::expected` result to handle potential system failures gracefully.

### Handling and returning responses

A response is either returned from a backend fetch or created locally (a "synthetic response") to handle redirects or error pages.

- **Upstream responses:** Take the successful return value from `fastly::Request::send()`.
- **Creation:** Instantiate `fastly::Response()` directly to create custom content.
- **Inspection:** Use `get_status()` or `get_headers()` to verify the backend's data.
- **Downstream (send to client):** Use `.send_to_client()` to stream the response back to the user. This is typically the final action your application takes.

> **NOTE:** Calling `.send_to_client()` begins the streaming process. Once called, you can no longer modify the response headers or status code.

## Parsing and transforming responses

The Fastly Compute platform is built for high-performance streaming. `Request` and `Response` in Compute services are streams, allowing large payloads to move through your service without running out of memory.

While you can read the entire body into memory using [`take_body_string()`](https://cpp-compute-sdk.fastly.dev/classfastly_1_1http_1_1Body.html#aa83967e4f51882340b68e2b79019c40d), this should be reserved for small payloads (like JSON metadata).

The following example reads a backend response into memory, replaces every occurrence of "cat" with "dog" in the body, and then creates a new body with the transformed string:

```cpp context="📄 src/main.cpp - Reading a response into memory"
#include <regex>
#include <fastly/sdk.h>

int main() {
    auto api_req{fastly::Request::get("https://host/api/checkAuth")};
    // section visible
    auto api_resp = api_req.send("example_backend");

    if (!api_resp) {
        fastly::Response{}.with_status(500).send_to_client();
        return 1;
    }
    auto api_resp_body = api_resp->take_body();

    // Take care! take_body_string() will consume the entire body into
    // memory, and regex_replace() will further double the memory requirement
    auto api_resp_body_string = api_resp_body.take_body_string();
    auto new_body = std::regex_replace(api_resp_body_string, std::regex{"cat"}, "dog");

    api_resp->set_body(new_body);
    // section-end visible
    api_resp->send_to_client();
}
```

For large streams, it is best to avoid loading the entire body into a single string or vector. Instead, use the body as a stream by calling `read()` in a loop to handle the contents piecewise. This allows your application to process massive payloads while maintaining a constant, minimal memory footprint.

In this example, we process a backend response 1 KiB at a time to calculate a basic checksum:

```cpp context="📄 src/main.cpp - Reading a response in a loop"
#include <fastly/sdk.h>
#include <vector>
#include <numeric>

int main() {
    auto resp_result = fastly::Request::get("https://origin.com/large-file")
        .send("my_backend");
    if (!resp_result) {
        fastly::Response{}.with_status(500).send_to_client();
        return 1;
    }

    // section visible
    auto body = resp_result->take_body();
    uint64_t total_checksum = 0;
    std::vector<uint8_t> buffer(1024); // Fixed 1 KiB buffer

    while (true) {
        auto read_result = body.read(buffer.data(), buffer.size());

        // Check for error or end of stream
        if (!read_result || read_result.value() == 0) {
            break;
        }

        // Perform calculation on the current chunk
        size_t bytes_read = read_result.value();
        total_checksum = std::accumulate(buffer.begin(), buffer.begin() + bytes_read, total_checksum);

        // The buffer is reused in the next iteration,
        // ensuring memory usage never exceeds ~1 KiB.
    }

    std::cout << "Processing complete. Checksum: " << total_checksum << std::endl;
    // section-end visible

    fastly::Response::from_status(200)
        .with_body("Processed " + std::to_string(total_checksum))
        .send_to_client();

    return 0;
}
```

C++ does not have built-in support for handling data such as HTML, JSON, XML, etc. Parsing these types of responses in C++ often benefits from well-tested third-party libraries. The following section describes an example of parsing JSON using such a library.

### Parsing JSON responses

For structured data, the [nlohmann/json](https://github.com/nlohmann/json) library is a popular choice that works well on the Compute platform.

To use it, add the following to your `CMakeLists.txt` using the same `FetchContent` pattern used for the SDK:

```cmake context="📄 CMakeLists.txt - Using FetchContent to add a library"
FetchContent_Declare(
    json
    GIT_REPOSITORY https://github.com/nlohmann/json.git
    GIT_TAG        v3.11.3
    SYSTEM
)
FetchContent_MakeAvailable(json)

# Link it to your executable
target_link_libraries(main PRIVATE nlohmann_json::nlohmann_json)
```

> **HINT:** Because modern C++ JSON libraries often rely on exceptions for error handling, ensure you have `JSON_NOEXCEPTION` defined or handle the `-fno-exceptions` flag as discussed in the ["no-exceptions" rule](https://www.fastly.com/documentation/guides/compute/developer-guides/cpp#the-no-exceptions-rule) section.

The following example attempts to parse a body as JSON, but limits the memory impact by only reading the first 4KiB of the stream.

```cpp context="📄 src/main.cpp - Parsing the first 4KiB of a body as JSON"
#include <nlohmann/json.hpp>
#include <fastly/sdk.h>

using json = nlohmann::json;

int main() {
    auto req = fastly::Request::from_client();
    auto resp_result = req.send("example_backend");

    if (!resp_result) {
        fastly::Response::from_status(500).send_to_client();
        return 1;
    }
    // section visible
    auto body = resp_result->take_body();

    // Limit read to 4KiB to protect memory
    std::vector<uint8_t> buffer(4096);
    auto read_result = body.read(buffer.data(), buffer.size());

    if (read_result && read_result.value() > 0) {
        // Trim buffer to actual bytes read
        buffer.resize(*read_result);

        // Parse JSON safely. Note: with -fno-exceptions,
        // nlohmann::json will call std::abort() on parse errors.
        auto data = json::parse(buffer, nullptr, false);

        if (!data.is_discarded()) {
            if (data.contains("status") && data["status"] == "success") {
                // ... logic based on JSON content ...
            }
        }
    }
    // section-end visible

    resp_result->send_to_client();
    return 0;
}
```

### Compression

Fastly can compress and decompress content automatically, and it is often easier to use these features than to try to perform compression or decompression within your C++ code. Learn more about [compression with Fastly](https://www.fastly.com/documentation/guides/concepts/compression).

## Using edge data

Fastly provides high-performance data stores for configuration and state. All edge data resources are account-level, [service-linked resources](https://www.fastly.com/documentation/guides/compute/edge-data-storage/about-edge-data-stores/), allowing a single store to be accessed from multiple Fastly services.

Accessing these stores in C++ requires handling `fastly::expected` (for system errors) and `std::optional` (for missing data). Therefore, you should use the `.has_value()` function and/or the `!` operator to check for and handle failures gracefully.

### Config store

Best for application configuration. Note that for Config Stores, the result is nested: an `expected` containing an `optional`.

```cpp context="📄 src/main.cpp - Config Store"
#include <fastly/sdk.h>

int main() {
    // section visible
    auto store_res = fastly::config_store::ConfigStore::open("app_config"); // expected
    if (!store_res) {
        std::cout << "Failed to open Config Store" << std::endl;
        fastly::Response::from_status(500).send_to_client();
        return 1;
    }

    auto val_res = store_res->get("api_url"); // expected<optional<string>>
    if (val_res && val_res->has_value()) {
        // Access the inner string via the value() of the optional
        std::cout << "API URL: " << val_res->value() << std::endl;
    }
    // section-end visible
    fastly::Response::from_status(200).send_to_client();
}
```

### Secret Store

Used for sensitive data. This follows a similar pattern to the Config Store but returns a `Secret` object that must be explicitly decrypted.

```cpp context="📄 src/main.cpp - Secret Store"
#include <fastly/sdk.h>

int main() {
    // section visible
    auto store_res = fastly::secret_store::SecretStore::open("my_secrets"); // expected
    if (!store_res) {
        std::cout << "Failed to open Secret Store" << std::endl;
        fastly::Response::from_status(500).send_to_client();
        return 1;
    }

    auto secret_res = store_res->get("api_key"); // expected<optional>
    if (secret_res && secret_res->has_value() ) {
        auto key = secret_res->value().plaintext(); // std::string
        // ... use key ...
    }
    // section-end visible
    fastly::Response::from_status(200).send_to_client();
}
```

### KV Store

The KV Store is unique because it treats values as **streams** (just like HTTP bodies) and tracks **versioning** metadata.

When you call `lookup()`, you get a `LookupResult` which provides:

- **`take_body()`**: Access the value as a `Body`.
- **`metadata()`**: Access any custom metadata associated with the key.
- **`current_generation()`**: A unique identifier for the specific version of the data (useful for concurrency control).

To write or update a value, call `insert()`.

```cpp context="📄 src/main.cpp - KV Store"
#include <fastly/sdk.h>

int main() {
    // section visible
    auto store_res = fastly::kv_store::KVStore::open("sessions"); // expected<optional>
    if (!store_res || !store_res->has_value()) {
        std::cout << "Failed to open KV Store" << std::endl;
        fastly::Response::from_status(500).send_to_client();
        return 1;
    }
    auto kv_store = store_res->value();

    // Read from KV Store
    auto entry_res = kv_store.lookup("user_123"); // expected
    if (entry_res && entry_res.has_value()) {
        auto body_str = entry_res->take_body().take_body_string();
        auto meta = entry_res->metadata();
        auto version = entry_res->current_generation();

        std::cout << "Data: " << body_str << " (Gen: " << version << ")" << std::endl;
    }

    // Write to KV Stores at runtime
    auto kv_store_write_result = kv_store.insert("user_123", "active");
    if (kv_store_write_result) {
        std::cout << "Wrote to KV Store!" << std::endl;
    }
    // section-end visible
    fastly::Response::from_status(200).send_to_client();
}
```

For more advanced usages of the KV Store, such as for enumerating keys or using the builder pattern to query or update entries, see the [C++ SDK documentation page](https://cpp-compute-sdk.fastly.dev/namespacefastly_1_1kv__store.html) on the KV Store.

## Logging

Observability is your primary window into how your logic behaves at the edge. The C++ SDK provides flexible ways to output data, ranging from standard C++ streams to specialized, high-performance log endpoints.

### Standard output logging (`std::cout`)

Any data sent to `std::cout` or `std::cerr` is captured by the Fastly runtime.

- **Locally:** Logs appear in your local testing server terminal.
- **Production:** Logs can be viewed in real-time using the `fastly log-tail` command.

```cpp
std::cout << "Processing request for: " << req.get_url() << std::endl;
```

For additional information on logging for testing and debugging, refer to the [Testing and debugging](https://www.fastly.com/documentation/guides/compute/developer-guides/cpp#testing-and-debugging) section below.

### Real-time log streaming

For production environments, you typically want to send data to a [named endpoint](https://www.fastly.com/documentation/guides/integrations/streaming-logs/) (e.g., S3, Datadog, or a custom syslog server). The SDK offers two ways to handle this:

- Global logging (`init_simple`)

   This is the easiest way to add logging across your entire application. You initialize a global logger with a specific endpoint and a severity filter.

  ```cpp context="📄 src/main.cpp - Global logging with init_simple()"
  #include <fastly/sdk.h>

  int main() {
      // section visible
      // Route all standard log calls to "my_endpoint_name"
      // and only send warnings or higher.
      fastly::log::init_simple("my_endpoint_name", fastly::log::LogLevelFilter::Warn);

      fastly::log::warn("This will be sent to the endpoint.");
      fastly::log::info("This will be ignored (below the 'Warn' threshold).");
      // section-end visible
  }
  ```
- Targeted logging (`Endpoint::from_name()`)

   Use this when you need to send different types of data to different places (e.g., sending "Audit Logs" to S3 and "Performance Metrics" to Datadog).

  ```cpp context="📄 src/main.cpp - Targeted logging with fastly::Endpoint::from_name()"
  #include <iostream>
  #include <fastly/sdk.h>

  int main() {
      // section visible
      auto audit_log = fastly::log::Endpoint::from_name("audit_logs");
      audit_log << "Sensitive security event detected." << std::endl;
      // section-end visible
  }
  ```

### Structured JSON logging

To make your logs searchable in production, send them as JSON. This allows your log provider to index specific fields like status codes, durations, or custom IDs.

```cpp context="📄 src/main.cpp - Structured JSON logging"
#include <iostream>
#include <fastly/sdk.h>
// section header
#include <nlohmann/json.hpp>
// section-end header

void log_analytics(uint64_t checksum) {
    // section visible
    nlohmann::json j = {
        {"event", "checksum_calculated"},
        {"value", checksum},
        {"timestamp", std::time(nullptr)}
    };

    auto endpoint = fastly::log::Endpoint::from_name("analytics");
    endpoint << j.dump() << std::endl;
    // section-end visible
}
```

### Comparison of Logging Methods

| Method                                   | Best Use Case           | Logic                                                  |
| :--------------------------------------- | :---------------------- | :----------------------------------------------------- |
| **`std::cout`**                          | Debugging / Prototyping | Standard stream; visible in `log-tail`.                |
| **`fastly::log::init_simple()`**         | General App Logging     | Global filter; maps to `fastly::log::info/warn/error`. |
| **`fastly::log::Endpoint::from_name()`** | Multi-destination       | Direct control over specific named endpoints.          |

## Using dependencies

The Compute build process compiles your C++ code to WebAssembly using the [WASI-SDK](https://github.com/webassembly/wasi-sdk). Because of this, it supports C++ libraries that are either **freestanding** or specifically compatible with the **WASI sysroot**.

### Integrating libraries with `FetchContent`

For external dependencies, the most efficient method is CMake's [FetchContent](https://cmake.org/cmake/help/v3.31/module/FetchContent.html) module. This automates the download and integration of source code during the configuration step.

```cmake context="📄 CMakeLists.txt - Adding nlohmann/json via FetchContent"
include(FetchContent)

FetchContent_Declare(
  nlohmann_json
  GIT_REPOSITORY https://github.com/github/nlohmann/json.git
  GIT_TAG        v3.11.3  # Can be a tag, branch, or commit hash
  SYSTEM                  # Suppresses warnings from dependency headers
)

FetchContent_MakeAvailable(nlohmann_json)

# Link the dependency to your application
target_link_libraries(your_app PRIVATE nlohmann_json::nlohmann_json)
```

### Compatibility requirements

While many modern, header-only, or freestanding C++ libraries work seamlessly, the WASI system interface has strict limitations.

| Feature Category     | Likely to Work? | Notes                                                                   |
| :------------------- | :-------------- | :---------------------------------------------------------------------- |
| **Header-only**      | ✅ Yes           | Libraries like `nlohmann/json` or `glm` are highly compatible.          |
| **Algorithms/math**  | ✅ Yes           | Pure computational logic (e.g., `Eigen`, `FastFloat`) works well.       |
| **Multithreading**   | ❌ No            | Libraries requiring `<pthreads.h>` or `std::thread` will fail to link.  |
| **Local networking** | ❌ No            | Access to `<sys/socket.h>` is not supported in the WASI sandbox.        |
| **OS frameworks**    | ❌ No            | Anything requiring a GUI (Win32, Cocoa) or local filesystems will fail. |

#### The "no-exceptions" rule

Fastly Compute applications must be compiled with `-fno-exceptions`. This is a requirement for the WebAssembly environment to ensure predictable performance and smaller binary sizes.

When integrating third-party libraries, keep the following in mind:

- **Library configuration:** Many libraries (like `nlohmann/json`) have specific macros to disable their internal exception handling (e.g., `#define JSON_NOEXCEPTION`).
- **Stubbing missing symbols:** If a library compiles but fails at the linking stage with errors like `undefined reference to __cxa_throw`, it means the library is still trying to use C++ exceptions. Sometimes, stubbing them can get them to work.
  > **HINT:** For example, in order to run Catch2, stub the following functions:
  >
  > ```cpp context="Stubbing missing exception symbols"
  > #ifdef __wasm__
  > extern "C" {
  > void *__cxa_allocate_exception(size_t) { return nullptr; }
  > void __cxa_throw(void *, void *, void *) { __builtin_trap(); }
  > void __cxa_free_exception(void *) {}
  > }
  > #endif
  > ```
- **Runtime behavior:** When exceptions are disabled, most libraries will call `std::abort()` or `__builtin_trap()` if they encounter a fatal error. Ensure your input validation is robust so these error paths are never triggered.

### Verifying whether a library is compatible

To verify if a library is compatible with Fastly Compute, attempt to configure your project with the WASI toolchain: `cmake --preset wasi`.

1. **Check for missing headers:** Errors pointing to `<sys/socket.h>`, `<pthreads.h>`, or `<dlfcn.h>` indicate the library relies on unsupported OS features.
2. **Watch for linker errors:** If the build fails at the linking stage with symbols like `pthread_create` or `fork`, the library is attempting to call unimplemented POSIX interfaces.
3. **Audit preprocessor macros:** Some libraries use `#ifdef _WIN32` or `#ifndef __wasm__` to provide "no-op" stubs. Be aware that these stubs may return errors or trigger a `std::terminate` at runtime.

> **HINT:** When using `FetchContent`, CMake automatically propagates your WASI toolchain settings to the dependency. This ensures the library is cross-compiled for WebAssembly specifically for your target environment.

## Link-time optimization (LTO)

To achieve the best possible performance and the smallest binary size, the C++ SDK supports Interprocedural Optimization (IPO), also known as [Link-Time Optimization (LTO)](https://llvm.org/docs/LinkTimeOptimization.html).

Standard compilation optimizes each `.cpp` file (translation unit) in isolation. LTO allows the linker to look across all files in your project to eliminate dead code and inline functions across the entire binary. This is particularly effective for WebAssembly, where binary size directly impacts startup time.

You can enable LTO in your `CMakeLists.txt` with the following block:

> **HINT:** This is included by default in the starter kit.

```cmake context="📄 CMakeLists.txt - Enable LTO to help reduce Wasm binary size and improve performance"
option(ENABLE_LTO "Enable cross language linking time optimization" ON)

if(ENABLE_LTO)
    include(CheckIPOSupported)
    check_ipo_supported(RESULT supported OUTPUT error)
    if(supported)
        message(STATUS "IPO / LTO enabled")
        # Tells the compiler to optimize across different translation units
        set_target_properties(main PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE)
        # Force the use of the LLVM Linker (lld), which is required for Wasm LTO
        target_link_options(main PRIVATE -fuse-ld=lld)
    else()
        message(STATUS "IPO / LTO not supported: <${error}>")
    endif()
endif()
```

By using LTO, you ensure that the final Wasm module is as lean as possible, reducing the "Cold Start" time of your service when it is first invoked at the edge.

## Testing and debugging

Logging to standard output (via `std::cout`, `printf()`, etc.) is the primary way to observe how your program behaves at the edge.

Log output from live services can be monitored via [live log tailing](https://www.fastly.com/documentation/guides/compute/developer-guides/testing/#live-log-monitoring-in-your-console). The local test server and Fastly Fiddle display all log output automatically. See [Testing & debugging](https://www.fastly.com/documentation/guides/compute/developer-guides/testing) for more information about choosing an environment in which to test your program.

- Local development server
   You can run your application on your local machine in a [local testing server](https://www.fastly.com/documentation/guides/compute/developer-guides/testing/#running-a-local-testing-server). It creates a local version of the Fastly environment, allowing you to run your Wasm binary and see exactly how it behaves before deploying.

   All `std::cout` and `std::cerr` output is piped directly to your terminal.

  ```term
  fastly compute serve
  ```

- Live log tailing
   Once your service is live, you can use log tailing to monitor `std::cout` and `std::cerr` output from your globally deployed application instances in real-time from your console. This is essential for debugging issues against real customer traffic, or for features that don't have support in the local testing server.

  ```term
  # View production logs as they happen
  fastly log-tail
  ```

- Fiddle
   If you are testing your application using the [Fastly Fiddle](https://fiddle.fastly.dev) tool, it displays `std::cout` and `std::cerr` output in the Fiddle interface.

### Debugging request and response bodies by "peeking"

Most common logging requirements involve inspecting HTTP requests and responses. However, since HTTP bodies are forward-only streams, they can only be consumed once. This means that simply reading a body to produce a log message will disrupt the rest of your program.

To solve this, you can use a body restoration pattern. To "peek" at a body, we read a small amount of data into a local buffer, then create a new body containing those bytes and append the original stream to it. Using a C++ Template allows this logic to work seamlessly for both Request and Response objects. With the template helper, logging request and response data becomes a clean one-liner that doesn't interfere with your `send()` or `send_to_client()` calls.

```cpp context="📄 src/main.cpp - \\"peeking\\" into request and response bodies"
#include <string>
#include <vector>
#include <fastly/sdk.h>

// Helper to handle optional headers cleanly
std::string header_val(const std::optional<fastly::http::HeaderValue> &header) {
    if (!header.has_value()) {
        return "";
    }
    return std::string(header->string().value_or(""));
}

// section peek_and_restore
// We use a template 'T' so this works for both Request and Response
template <typename T>
std::string peek_and_restore(T& message, size_t len) {
    // 1. Take the body from the message
    auto body = message.take_body();

    // 2. Read the prefix
    std::vector<uint8_t> buffer(len);
    auto read_res = body.read(buffer.data(), buffer.size());

    if (!read_res || *read_res == 0) {
        message.set_body(std::move(body)); // Put it back if empty
        return "";
    }

    size_t n = *read_res;
    std::string prefix(reinterpret_cast<char*>(buffer.data()), n);

    // 3. Restore the body by prepending the bytes we read
    auto restored_body = fastly::Body();
    if (restored_body.write(buffer.data(), n)) {
        restored_body.append(std::move(body));
    }

    // 4. Put the restored body back into the message
    message.set_body(std::move(restored_body));

    return prefix;
}
// section-end peek_and_restore

int main() {
    auto req = fastly::Request::from_client();

    // section log_request
    std::cout << "Request:" << std::endl;
    std::cout << "User-Agent: " << header_val(req.get_header("User-Agent").value()) << std::endl;
    std::cout << "POST Body: " << peek_and_restore(req, 1024) << std::endl;
    // section-end log_request

    auto beresp_res = req.send("origin_0");
    if (!beresp_res) {
        fastly::Response::from_status(500).send_to_client();
        return 1;
    }

    auto &beresp = beresp_res.value();

    // section log_response
    std::cout << "Response:" << std::endl;
    std::cout << "Content-Type: " << header_val(beresp.get_header("Content-Type").value()) << std::endl;
    std::cout << "Response Prefix: " << peek_and_restore(beresp, 20) << std::endl;
    // section-end log_response

    beresp.send_to_client();

    return 0;
}
```

> **HINT:** This pattern is highly efficient. Only the "peeked" bytes are buffered in memory; the rest of the body continues to stream through the Fastly host handles.

### Handling WebAssembly traps

When a C++ program performs an illegal operation (like accessing a failed `fastly::expected` via `.value()`), it "Traps."

- **In the local testing server:** You will see a detailed error message and potentially a stack trace in your terminal.
- **In production:** The visitor will receive a `500 Internal Server Error`.

> **IMPORTANT:** In a production-ready service, always check `expected.has_value()` before calling `.value()`. A trap in production is difficult to debug because it provides no custom log data before termination.

### Unit testing

Because the Fastly C++ SDK relies on WebAssembly hostcalls (functions provided by the Fastly runtime), you cannot use standard native tools to test SDK-dependent code directly. If you try to run a test that would access a `fastly` object, the program will crash because the Host environment isn't there to answer.

One recommended strategy is to employ the **Interface/Logic separation** pattern. By writing your application logic to accept generic interfaces rather than concrete SDK objects, you can run tests against "Mock" classes without relying on code that invokes hostcalls.

[Catch2](https://catch2.org) is a simple testing framework that is compatible with the Compute C++ SDK. This section describes how to set up a C++ application that uses Catch2 with this strategy.

#### Project structure

Use a standard C++ layout to keep your headers, implementation, and tests distinct:

```text
my-service/
├── include/
│   ├── interfaces.h     # Abstract base classes
│   └── utils.h          # Headers for utils.cpp
├── src/
│   ├── main.cpp         # The Fastly entry point
│   └── utils.cpp        # Business logic implementation
├── tests/
│   ├── main.cpp         # Test entry point
│   └── test_utils.cpp   # Catch2 test cases
├── CMakeLists.txt
├── CMakePresets.json
└── fastly.toml
```

In our somewhat-contrived example below, we will be writing and testing `is_mobile()`, a function that tests a request's `User-Agent` header to detect whether it has been sent from a mobile device.

#### Define the interface

Instead of passing a `fastly::Request` directly to your functions, we will define an interface that lets the caller get a header value. This allows you to swap a real Request for a "Mock" Request during testing.

```cpp context="📄 include/interfaces.h"
#pragma once
#include <string>
#include <optional>

// An interface for any object that can provide headers
class HeaderSource {
public:
    virtual ~HeaderSource() = default;
    virtual std::optional<std::string> get_header(const std::string& name) const = 0;
};
```

```cpp context="📄 src/utils.h"
#pragma once
#include "interfaces.h"

bool is_mobile(const HeaderSource& source);
```

#### Write the logic

This is the code we want to test. The business logic remains "pure". It doesn't know about the Fastly SDK; it only knows about the `HeaderSource` interface.

```cpp context="📄 src/utils.cpp"
#include "utils.h"

// Logic that determines if a request is from a mobile device
bool is_mobile(const HeaderSource& source) {
    auto ua = source.get_header("User-Agent");
    if (!ua) return false;
    return ua->find("Mobile") != std::string::npos;
}
```

#### Write the Catch2 test cases

Using Catch2, we can structure our tests into `TEST_CASE` and `SECTION` blocks for high readability.

```cpp context="📄 tests/test_utils.cpp"
#include <catch2/catch_test_macros.hpp>
#include "interfaces.h"
#include "utils.h"

// A Mock implementation for testing
class MockSource : public HeaderSource {
    std::string mock_ua;
public:
    MockSource(std::string ua) : mock_ua(ua) {}
    std::optional<std::string> get_header(const std::string& name) const override {
        if (name == "User-Agent") return mock_ua;
        return std::nullopt;
    }
};

TEST_CASE("Device detection logic", "[utils]") {
    SECTION("Detects mobile devices") {
        MockSource mobile_req("Mozilla/5.0 (iPhone; CPU iPhone OS 14_0) Mobile");
        CHECK(is_mobile(mobile_req) == true);
    }

    SECTION("Detects desktop devices") {
        MockSource desktop_req("Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
        CHECK(is_mobile(desktop_req) == false);
    }

    SECTION("Handles missing User-Agent") {
        MockSource empty_req("");
        CHECK(is_mobile(empty_req) == false);
    }
}
```

#### The test entry point

Since we compile with `-fno-exceptions`, we need to define `CATCH_CONFIG_RUNNER` and provide a `main()` to run our Catch session.

```cpp context="📄 tests/main.cpp"
#define CATCH_CONFIG_RUNNER
#include <catch2/catch_all.hpp>

#ifdef __wasm__
extern "C" {

// Stubs needed to get Catch2 to link
void *__cxa_allocate_exception(size_t) { return nullptr; }
void __cxa_throw(void *, void *, void *) { __builtin_trap(); }
void __cxa_free_exception(void *) {}

}
#endif

int main(int argc, char* argv[]) {
    // Add wasi-specific setup here if needed
    return Catch::Session().run(argc, argv);
}
```

#### Build configuration

To run these Catch2 tests, we compile them into a WebAssembly binary and execute them inside Viceroy, the local testing server.

```cmake context="📄 CMakeLists.txt (snippet) - Add test configuration"
# Catch2 testing utility
FetchContent_Declare(
        Catch2
        GIT_REPOSITORY https://github.com/catchorg/Catch2.git
        GIT_TAG        v3.4.0
        SYSTEM
)
add_compile_definitions(CATCH_CONFIG_NO_POSIX_SIGNALS)
add_compile_definitions(CATCH_CONFIG_DISABLE_EXCEPTIONS)
FetchContent_MakeAvailable(Catch2)

add_executable(unit_tests
        tests/main.cpp
        tests/test_utils.cpp
        src/utils.cpp
)
target_include_directories(unit_tests PRIVATE include)

if(NOT BUILD_TESTING)
    # NOTE - Move this step into here
    target_link_libraries(main PRIVATE
            fastly::fastly
    )
    # Don't build unit_tests app
    set_target_properties(unit_tests PROPERTIES EXCLUDE_FROM_ALL TRUE)
else()
    # Link the unit_tests app
    target_link_libraries(unit_tests PRIVATE
            fastly::sdk
            Catch2::Catch2WithMain
    )
    # Don't build the main app
    set_target_properties(main PROPERTIES EXCLUDE_FROM_ALL TRUE)
endif()
```

```json context="📄 CMakePresets.json (snippet) - Add preset"
{
  "name": "wasi-tests",
  "generator": "Unix Makefiles",
  "binaryDir": "${sourceDir}/build-tests",
  "cacheVariables": {
    "CMAKE_BUILD_TYPE": "Debug",
    "BUILD_TESTING": "ON",
    "CMAKE_TOOLCHAIN_FILE": "/opt/wasi-sdk/share/cmake/wasi-sdk-p1.cmake"
  }
}
```

#### Running the tests

Now you can build your test suite as a Wasm binary and execute it using Viceroy. Viceroy provides the necessary WebAssembly environment so the Wasm binary can execute safely.

```term
# 1. Configure and Build
cmake --preset wasi-tests
cmake --build build-tests

# 2. Run the tests inside the Viceroy simulation
viceroy run build-tests/wasm_tests.wasm
```

The following example output is produced:

```text
Randomness seeded to: 1154335898
===============================================================================
All tests passed (3 assertions in 1 test case)
```

This workflow gives you the best of both worlds: your code is architected to be modular and testable, but you are still verifying the final binary in the actual WebAssembly environment it will inhabit.

#### The application entry point

In your application's code, you:

- **Create a concrete implementation** of the interface that talks to the Fastly SDK
- **Coordinate the execution** by wrapping the incoming request and passing it to your business logic

```cpp context="📄 src/main.cpp"
#include <fastly/sdk.h>
#include "interfaces.h"
#include "utils.h"

// section implementation
// Concrete implementation of HeaderSource interacts with actual Fastly Request object.
class FastlyHeaderSource : public HeaderSource {
    fastly::Request& _req;
public:
    FastlyHeaderSource(fastly::Request& req) : _req(req) {}

    std::optional<std::string> get_header(const std::string& name) const override {
        auto res = _req.get_header(name);  // returns expected<optional>
        if (res && res->has_value() && res->value().string().has_value()) {
            return std::string(res->value().string().value());
        }
        return std::nullopt;
    }
};
// section-end implementation

int main() {
    // section main
    // 1. Initialize the incoming request from the client
    auto req = fastly::Request::from_client();

    // 2. Wrap the request in our interface implementation
    FastlyHeaderSource source(req);

    // 3. Use our testable business logic!
    bool mobile = is_mobile(source);

    // 4. Build and send the response
    auto resp = fastly::Response::from_status(200);
    resp.with_body(mobile ? "Hello, mobile user!" : "Hello, desktop user!");
    resp.send_to_client();
    // section-end main

    return 0;
}
```

By structuring your code this way, your `main.cpp` becomes a thin "adapter" layer.

- **The SDK is isolated:** Only this file and your `FastlyHeaderSource` know about the Fastly SDK types.
- **The logic is pure:** Your `utils.cpp` stays clean, portable, and 100% testable on any platform.
- **The tests are fast:** You can run thousands of unit tests against your `is_mobile` logic using the `MockSource` without ever needing to compile a Wasm binary or start Viceroy.

## Key takeaways and next steps

The Fastly C++ SDK gives you the closest possible access to the metal of the edge. Now that you have the tools to build, test, and debug effectively, you're ready to push the boundaries of what's possible at the network's edge.

As you work with C++ on Fastly Compute, keep in mind the following:

- **Embrace move semantics:** Understand that `fastly::Body` and `fastly::Request` are move-only resources that represent live system handles.
- **Trust but verify:** Handle the `expected<T>` return values to navigate the layers of potential failure at the edge.
- **Architect for testability:** Keep your core logic "pure" so your code is testable without relying on concrete Fastly object.

## Related content

- [Fastly C++ SDK reference](https://cpp-compute-sdk.fastly.dev)
- [Fastly C++ SDK source code on GitHub](https://github.com/fastly/compute-sdk-cpp)
- [(Blog Post) Unparalleled Performance: Bring Your C++ Logic to the Edge](https://www.fastly.com/blog/unparalleled-performance-bring-your-logic-edge)
