# Creating a Project Template? Go with API

[Tomáš Kocman](https://www.strv.com/blog/authors/tomas-kocman)  
Go Backend Engineer

---

The API layer, or what we often call the transport layer, is the last but crucial part of the “STRV Go Template.” All the fancy things with databases, architectures, and designs that I wrote about in the previous parts are important, but every application needs to have a solid entry point where we can determine which business logic branch we should take, somehow validate input parameters, and return a proper response, either expected or an error. With many ways to implement this layer, how do we choose? Why? And are there best practices?

I'll share our perspective, real examples, and introduce our open-source Go packages to **help you build your next breakthrough application**. You’ll find out how to properly structure the transport layer and choose the right documentation strategy. If you enjoyed the generic helpers from the previous article, expect more here!

## Transport and Other Layers

The relationship between the transport and service layers is pretty straightforward. **The transport layer handler serves as the application’s entry point**, where input parameters are parsed and validated. It’s also where the response from the **service layer** is serialized to the transport layer protocol and sent back to the API caller. The **service layer function**, in turn, is called by the transport handler and is responsible for returning the result of the API call by contacting dependencies, which may include other application services, third-party services like AWS or GCP, or databases. Speaking of which, the **database** is the third and final layer. Its role is to satisfy all operations required by the service to meet client requests, such as storing, fetching, listing, or deleting domain entities.

The diagram depicts the relationship between all the layers. The service layer is called by handlers because the request must be fulfilled by a service method. The database layer is directly called by the service method. **Dataloader** for optimized data fetching is a wrapper around the database and is called by the service method to eliminate communication between transport and database layers.

It’s very important to note: when I mention calling the service layer, everything happens via interfaces. You already know how **we use domain-driven design** and have seen example domains—session and user. There are also services (domain/application/infrastructure) that represent a composition of all possible operations with domains, which form the interfaces in the transport layer.

```go
type UserService interface {
    Read(ctx context.Context, userID uuid.UUID) (*domuser.User, error)
}

type SessionService interface {
    Destroy(ctx context.Context, refreshTokenID uuid.UUID) error
}
```

In this example, we see the mapping between API handlers and service functions, regardless of protocol. Having one universal interface for all services contradicts the Go philosophy and isn't practical.

## HTTP Server

Once you understand how the transport layer communicates with the service layer, let’s peek under the hood. It’s an **HTTP server**. Setting it up is straightforward: instantiate the native Go HTTP server and provide an HTTP handler.

To make this process easier, we've built our custom [net package](https://pkg.go.dev/go.strv.io/net), which wraps the native HTTP server. This package offers helpers for writing APIs. **The Go team at STRV is constantly improving our open-source packages**, though most are not yet at v1 — we’re still battle-testing before releasing a major version. If you encounter issues, create an issue on GitHub!

Here's a basic server configuration:

```go
serverConfig := httpx.ServerConfig{
    Addr: addr,
    Handler: controller,
    Hooks: httpx.ServerHooks{
        BeforeShutdown: []httpx.ServerHookFunc{
            func(_ context.Context) {
                database.Close()
            },
        },
        Limits: nil,
        Logger: util.NewServerLogger("httpx.Server"),
    },
}
```

We always rename our open-source package imports to avoid conflicts — here, it’s `httpx "go.strv.io/net/http"`. The `addr` is a network address (e.g., ":8080"). The `controller` is our custom HTTP handler, whether REST or GraphQL. `BeforeShutdown` allows running operations before the server stops—like closing databases or wrapping up HTTP connections. The server stops receiving new requests but completes in-progress ones and closes the database.

For a simple server, configuring limits isn’t necessary, but it's optional. If not set, defaults are used. The last field is `Logger`, which implements a logging interface. We use [zap](https://pkg.go.dev/go.uber.org/zap), but with the recent release of [slog](https://pkg.go.dev/log/slog), we’re watching for future adoption; currently, zap remains our standard.

Next, initialize and run the server:

```go
server := httpx.NewServer(&serverConfig)
if err = server.Run(ctx); err != nil {
    logger.Fatal("HTTP server unexpectedly ended", zap.Error(err))
}
```

Providing config and starting the server is straightforward. In upcoming sections, I’ll cover the implementation of the controller, where the core logic happens.

## Which API To Use

The HTTP server works with any handler, regardless of transport technology. However, **choosing the architectural style of the transport layer is not trivial**. Factors influencing this decision include style benefits and trade-offs: REST, GraphQL, and gRPC.

- **gRPC** is rarely used in our projects because it’s more suitable for server-to-server internal communication than client-server. The template is extendable; support for gRPC can be added if needed in the future.

- **REST and GraphQL** are prevalent options. Developers can pick their preference. Remember the [Clash of APIs](https://www.strv.com/blog/clash-of-apis-rest-vs-graphql) blog post? My colleague breaks down REST vs. GraphQL there.

Ultimately, team expertise influences choice—some projects benefit from one or the other based on the engineers' familiarity. Flexibility is key: support both and let the team decide what fits best as long as the API remains clear and maintainable.

## Design Process

Let’s shift from tech details to the design process—crucial when starting a project. Our team follows a **design-first** approach: always start by writing an OpenAPI or GraphQL schema before implementing functionality. Unlike code-first, which generates documentation from code annotations, design-first keeps everyone on the same page and helps parallelize frontend, testing, and development.

This approach improves communication, reduces time-to-market, and yields more reliable documentation and testing. Discussing features and understanding different viewpoints produce better APIs.

## REST

Back to technicalities. We won’t deep dive into REST specifics but will outline **the technologies and helpers we've built in our open-source package**, making handler code simpler.

We don’t generate code from OpenAPI specs yet, preferring to write models with validation ourselves. When we do generate, it will be for models that are compatible with [validator](https://pkg.go.dev/github.com/go-playground/validator/v10).

### Controller

A controller implements `ServeHTTP`, often using [Chi](https://pkg.go.dev/github.com/go-chi/chi/v5) as a router, with middlewares for CORS, request ID, logging, panic recovery, request size limits, and more. Helper endpoints like health or OpenAPI docs are included, but core business logic endpoints are in versioned routes (e.g., v1).

Example of a v1 handler setup:

Handlers are tied to the controller. They implement business logic, with middleware for auth, validation, etc. Each handler defines interfaces to be satisfied by the service layer, as shown earlier.

API models are also defined here, validated via the [validator package](https://pkg.go.dev/github.com/go-playground/validator/v10). Creating a helper to parse and validate request bodies is recommended, returning details about validation errors.

### Helpers

Parsing requests involves parsing path and query params. We use a generic:

```go
type ParamUnmarshaller interface {
    UnmarshalText(data []byte) error
}

func GetPathParam[TParam any, TPtrParam interface {
    *TParam
    ParamUnmarshaller
}](r *http.Request, paramName string) (pathParam TParam, err error) {
    p := TPtrParam(new(TParam))
    if err = p.UnmarshalText([]byte(chi.URLParam(r, paramName))); err != nil {
        return pathParam, err
    }
    return *p, nil
}
```

Usage:

```go
objectID, err := GetPathParam[id.User](r, "userId")
```

For query params, we recommend the [param subpackage](https://pkg.go.dev/go.strv.io/net/http/param), which parses input requests, including path and query params, in one go.

Handling errors is improved by translating domain errors into HTTP responses. Use `errors.As()` to convert errors, and enrich errors with context information like message and data. The open-source [http subpackage](https://pkg.go.dev/go.strv.io/net/http) offers helpers for this, including `WriteErrorResponse` and `WriteResponse` functions:

```go
err := httpx.WriteErrorResponse(w, statusCode, opts...)
err := httpx.WriteResponse(w, data, statusCode, opts...)
```

Options are variadic, with defaults provided if omitted. Handler functions can be wrapped using the [signature subpackage](https://pkg.go.dev/go.strv.io/net/http/signature), reducing boilerplate.

## GraphQL

GraphQL handling is simpler in some ways, as third-party libraries manage many aspects. We still follow a design-first methodology, collaborating with the team on schemas.

The controller for GraphQL over HTTP is similar to REST but differs mainly in authentication/authorization handling. Unlike REST, GraphQL generally avoids versioning; it’s designed for continuous schema evolution ([more here](https://graphql.org/learn/best-practices/)).

## Code Generator

We use [gqlgen](https://pkg.go.dev/github.com/99designs/gqlgen). It generates code from GraphQL schemas, including request handling, maintaining type safety and reducing boilerplate. The generator creates the project skeleton, and we add the necessary controllers.

Features include custom models, federation, hooks, and validation. We recommend creating a custom error presenter to unify error handling, logging, and contextual info, similar to REST.

## Data Loaders

GraphQL can face the <em>n+1</em> problem. Data loaders batch requests, reducing queries. We use a [third-party package](https://github.com/graph-gophers/dataloader), based on generics, inserted into context via middleware. This approach improves performance but can be added later as needed.

## Conclusion

Now you have a rough idea of the “STRV Go template.” I hope the shared knowledge proved valuable, especially from parts one and two, about domain-driven design and database strategies. 

Finally, I want you to understand our mindset regarding API technologies—how we pick styles, and how our open-source tools streamline development. Reach out if you'd like guidance applying these practices. Let your APIs flourish!