Building APIs with Go — Part 2 Writing our first endpoints

We’ve reviewed some frameworks and routers on the previous part: Building APIs with Go — Part 1 Choosing our router | by Fernando H. Bandeira | Apr, 2021 | Medium

Now that we’ve chosen our router, we can proceed and build our first endpoints for the API.

We will be defining a directory structure and building a simple Todo API. As we advance in the series, we’ll be adding extra features to this API.

The code of this part is available on Github: fernandobandeira/go-api at part-2 (github.com)

First, let’s take a look at some architectural standards:

DDD, Hexagonal, Onion, Clean, CQRS, … How I put it all together — @hgraca (herbertograca.com)

To summarize, the goal of those standards are pretty simple. We want to separate our business logic from our UI and infrastructure.

Thinking about a functional approach, a request would be just a sequence of function calls starting from the UI (Rest API handlers in our case), which calls our business logic, which in turn makes calls to our infrastructure (repositories, email, payment processor, etc.), after that it returns to the client.

Note: There’s a repo called golang-standards/project-layout with some “standards” used by the community. However, those aren’t actual project standards: this is not a standard Go project layout · Issue #117 · golang-standards/project-layout (github.com)

We will start coding now. We will follow a directory structure based on what we discussed in the picture above.

entities/todo.go

Entities have their own folder. They are the core of our domain. They can also be used as a DB model, this causes a bit of coupling with the infrastructure layer but it should be alright. In this example, we’re using Gorm. However, we may reconsider this in the future when we do some benchmarking and explore ways to improve the response time, typically with Go, you’d use the native drivers.

We also have a factory inside the entity. You may include other business-related logic in this file. If this were a user, for instance, we’d be hashing the password inside this method. This way, we’d be able to reuse this in many places (testing, for example) without rewriting the business logic.

One thing to keep in mind is that if you want to set a field to null on the database, you have to use the native SQL nullable types or use this package: https://github.com/guregu/null instead of the type string like we did here.

interfaces/todo.go

In Go, there’s a lot of benefits for creating interfaces. To start, you can decouple different parts of your code. You can also generate mocks for these interfaces later and use them to test your code independently.

Here we’re creating a folder and putting them inside. This will concentrate all of our interfaces in a single place. Another benefit is that we’ll be less likely to run into circular dependencies issues, especially when testing.

repositories/todo.go

The repository will be interacting with the Postgres database for us. You may want to have other databases or cache some of these queries using Memcached or Redis. We may look into this in another part. In this case, you would abstract the cache logic inside the repository and then implement adapters for Postgres and Memcached, calling them inside this file.

services/todo.go

Right now, the service is just calling our repository and not really doing anything else. We will be adding more logic here as we progress. We can send emails when a todo is created, for example, or perform other actions.

api/requests/todo.go

It’s useful to have a separate struct for the requests instead of using the entity. You may want to limit what fields are available on certain requests. For instance, you may want to allow users to provide an email when creating their account, but maybe you don’t want them changing their email afterward.

Sometimes you may also have a presenters or responses folder containing a struct for the responses. You’d do that when you want to serve different responses for the same entity. Most of the time, you can use the entity itself.

utils/json.go

To reduce a bit of duplicated code, we’re using a JSON helper to read and write JSON responses. We’re using the standard library currently, but we may look into alternatives in another part when we do some benchmarks.

api/handlers/todo.go

Since we are building a rest API, the handlers are our main entry point. They will be receiving and validating requests, calling the services, and formatting the responses.

You can see that we have a lot of duplicated code here for handling the errors. We will look at ways of optimizing this in the next part.

Instead of serving requests, you could be consuming a message queue or receiving RPC calls. You can use the same service that we’ve created before that we’re using here for these other entry points.

main. go

The main file combines everything we’ve coded so far, injecting the necessary dependencies, creating the main router, and listening to incoming requests.

Later we may want to add configurations for different environments and optimize this file for e2e tests.

Right now, we have this folder structure:

  • /api/handlers
  • /api/requests
  • /entities
  • /interfaces
  • /repositories
  • /services
  • /utils

As we’ve seen at the beginning of the post, the API contains our entry points, it then calls our service (application layer), the service is using our entities (domain models) and fetching data from our repository (infrastructure), the request then goes back to the entry point where it’s formatted and presented to the user.

We can easily change our database or serve different requests and change the router without touching our business logic (services and entities). Of course, the entity is also being used as a model for gorm, but it’s not a tight coupling since we can easily swap it with another database.

This is the end of part 2. We will be looking into instrumentation with logging, tracing, metrics, and error handling in the next part.

Let me know if you have questions or suggestions in the comments, and as always, thanks for reading till here.

The code of this part is available on Github: fernandobandeira/go-api at part-2 (github.com)

CTO @ Goodworld