# Creating a Project Template? Go with API

[Tomás 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, 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 all necessary dependencies. These dependencies 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 the client's requests, such as storing, fetching, listing, or deleting domain entities.

The diagram depicts the relationship between all the layers described above. The service layer is called by the 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 so communication between transport and database layers is eliminated.

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

```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 the example above, we see the mapping between API handlers and service functions, no matter what protocol is used. Having one universal big interface for all services goes against the Go philosophy and isn’t even practical.

## HTTP Server

When you have a basic understanding of how the transport layer communicates with the service layer, let’s peek under the hood. As you probably guessed — **it’s an HTTP server**. Setting it up is relatively easy. In short, you instantiate the Go native HTTP server and provide an HTTP handler.

We wanted to make this initialization process even friendlier, so we’ve built our custom [net package](https://pkg.go.dev/go.strv.io/net) that wraps the native HTTP server. Actually, this functionality is just one part of the package. Overall, it provides a whole range of helpers for writing APIs. **The Go team at STRV is constantly improving our open-source packages**, and you may notice most of them (if not all) are not released as v1 yet — we’re still battle-testing our tools on real projects before releasing with a major version. But hey, if you encounter any problems, make sure to create an issue on GitHub!

For now, let’s focus on the server functionality of the HTTP subpackage and define 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 imports of our STRV open-source packages to prevent conflicts with the standard ones. In this case, we import the package as *httpx* from `"go.strv.io/net/http"`. The *addr* variable is a network address, e.g., “:8080”. The *controller* variable is our custom HTTP handler, whether REST or GraphQL API. *BeforeShutdown* is useful for operations that need to be done before the HTTP server stops — like stopping databases or wrapping up HTTP connections your server has instantiated. When the server stops, it won’t accept new requests, but **we want to complete the ones in progress and close the database**.

For a simple HTTP server, you don’t even need to configure limits, but you have the option. If you don’t define the *Limits* object, default values for the Go server are applied. You can check the *Limits* data structure in the documentation.

The last field in the config is *Logger*, which is the interface you need to implement. Currently, we still use the [zap](https://pkg.go.dev/go.uber.org/zap) package for logging, wrapping it to match the interface. Although [slog](https://pkg.go.dev/log/slog), the Go native structured logging package, was recently released, we still prefer zap. *slog* is on our enhancement pipeline with low priority, since zap remains one of the best third-party logging options.

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 a server config and running the server is straightforward; the process doesn’t need further elaboration. In the following sections, I’ll describe the implementation of the controller, where the real heavy lifting happens.

## Which API To Use

The HTTP server works with any handler, regardless of transport layer technology. But **choosing the architectural style of the transport layer is not trivial**. Major styles today are REST, GraphQL, and gRPC. Each comes with benefits and trade-offs. I won’t dive deep into each, assuming familiarity, but instead focus on how we decide which to use.

We almost never use **gRPC**. Its use cases don’t align well with our client projects, which lean towards client-server communication rather than internal server-to-server communication. The template is extendable, so if needed, support for gRPC can be added in future.

We support **REST and GraphQL**, which are prevalent in our projects. Developers can pick their preferred style when starting a new project. Remember the [Clash of APIs](https://www.strv.com/blog/clash-of-apis-rest-vs-graphql) blog post? My colleague Mr. Viking breaks down the differences there.  

In my experience, besides technical suitability, **team expertise and familiarity** often take precedence. For example, we proposed REST for backend but frontend engineers preferred GraphQL; in another case, iOS engineers leaned toward REST even when we suggested GraphQL. Success often boils down to team skills rather than technology choice. The goal is to architect a readable and maintainable API — technology is secondary.

## Design Process

Let’s shift focus to the design process, a key step when starting a project. Note: I speak for the Go backend team, not the whole backend department. **Sometimes the process or style is influenced by tooling**, which explains differences across teams and projects.

Our process is **design-first**: we start implementation by writing an OpenAPI/GraphQL spec before coding. This contrasts with code-first, where documentation is generated from code annotations. 

**Why design-first?** It aligns everyone, speeds up development, ensures consistent documentation, and improves testing. It also enhances communication, as discussing the API with front-end or test engineers leads to better overall design.

Most importantly, whether you pick code-first or design-first, *regular communication and collaboration on core features* are vital.

## REST

Next, let's discuss REST API style. I won’t go into fundamentals but share what tools and helpers we have developed in our open-source package to simplify handler methods.

Currently, we don’t generate code from OpenAPI; we prefer writing models and validation ourselves. When we find a generator that fits our validator package, we’ll consider it.

**Controller**

A controller implements `ServeHTTP`. We use [Chi](https://pkg.go.dev/github.com/go-chi/chi/v5) as a router, with general middlewares: CORS, request ID, logging, panic recovery, request size limiting, etc., some available in our [http package](https://pkg.go.dev/go.strv.io/net/http). These middlewares wrap all API endpoints and helper routes, like health checks or OpenAPI rendering. The core endpoints are business logic handlers, starting with version v1, with room for future versions.

For example, v1 handlers are connected to the controller and include authentication and authorization middlewares. Each handler implements its own interface according to the service layer.

API models are also defined here, using the [validator](https://pkg.go.dev/github.com/go-playground/validator/v10). We recommend helper functions to parse request bodies and perform validation, returning errors with details if needed.

**Helpers**

For parsing requests, especially path and query parameters, we use a generic function:

```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, a similar helper can be implemented. For more advanced parsing, our [param subpackage](https://pkg.go.dev/go.strv.io/net/http/param) helps parse body, path, and query parameters efficiently in one step.

**Error translation**

Handling errors returned from the service layer properly is important. Use `errors.As` to interpret domain errors and map properties like `Code`, `Message`, and `Data` to HTTP status and response content.

When returning JSON, we use helpers from the [http subpackage](https://pkg.go.dev/go.strv.io/net/http), especially for creating error responses:

```go
opts = []httpx.ErrorResponseOption{
    httpx.WithError(domainErr.Err),
    httpx.WithErrorCode(string(domainErr.Code)),
    httpx.WithErrorMessage(domainErr.Message),
    httpx.WithErrorData(domainErr.Data),
}
err := httpx.WriteErrorResponse(w, statusCode, opts...)
```

Similarly, success responses:

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

Options are variadic; defaults are used if omitted.

Handler wrapping simplifies repetitive tasks: parsing inputs, handling errors, and writing responses are abstracted via a **signature** helper. All helpers are optional, and engineers can choose to use or skip them.

## GraphQL

GraphQL is simpler in some ways because third-party libraries handle much. Core principles remain: **design-first**, collaborating on schema, iterating versioning, and avoiding versioning if possible.

**Controller**

GraphQL controllers run over HTTP, with middleware for CORS, request IDs, logging, etc., same as REST. Authentication is via HTTP middleware, while authorization is implemented as GraphQL directives, similar to middleware.

GraphQL foregoes versioning, favoring schema evolution. Read more in [GraphQL best practices](https://graphql.org/learn/best-practices/).

**Code Generation**

We use [gqlgen](https://pkg.go.dev/github.com/99designs/gqlgen), which generates code from schemas, reducing boilerplate. It generates resolvers, input types, enums, federation support, hooks, and more, with a focus on type safety.

Create a custom error presenter for uniform error handling, logging domain errors and enriching responses accordingly.

**Data Loaders**

GraphQL's nested requests can cause the **n+1** problem. Data loaders batch database requests for efficiency and caching, often used via a third-party [package](https://github.com/graph-gophers/dataloader).

Implementing data loaders is optional; start without, then add as needed for performance.

## Conclusion

Now you have a rough understanding of the “STRV Go template.” I hope the insights from [part one](https://www.strv.com/blog/creating-a-project-template-go-with-domain-driven-design), where we introduced domain-driven design, and [part two](https://www.strv.com/blog/creating-a-project-template-go-with-database-storage), focused on databases, have been useful.

This final part aims to convey our mindset around API tech choices, how we pick styles, and leveraging open-source packages to streamline development. If you’re unsure how to adopt these practices, reach out. Let your APIs flourish!