Building a Microservices Cloud Backend using Go and PostgreSQL
Marcus Kohlberg
Posted on December 6, 2023
π¬ Building microservices applications can be a pain. Normally you need to deal with a lot of boilerplate, and it can be hard to ensure end-to-end type-safety.
π‘ This guide shows you how to deploy a fully type-safe microservices application in Go β implementing the backend for a Trello application as an example.
This is what our backend architecture will look like, consisting of two services each with their own PostgreSQL database:
To build our app, we'll be using Encore, a backend development platform that provides a type-safe Infrastructure SDK for declaratively defining infrastructure in Go code.
We'll then use Encore to automatically provision and deploy our application.
π What's on deck:
- Install Encore
- Create your backend app from a template
- Run locally
- Deploy to Encore's free development cloud
π½ Install Encore
Install the Encore CLI to run your local environment:
-
macOS:
brew install encoredev/tap/encore
-
Linux:
curl -L https://encore.dev/install.sh | bash
-
Windows:
iwr https://encore.dev/install.ps1 | iex
π¨ Create your app
We'll be starting from a template that has two services, each with a couple of API endpoints and their own databases. (See the architecture diagram above.)
When you have installed Encore, create your application from the example app using this command:
encore app create my-app-name --example=trello-clone
π Run your app locally
Before running the application, make sure you have synced the project dependencies by running go mod tidy
from the project root folder.
Also make sure that Docker is installed and running, it's required when running Encore applications locally that use SQL databases.
With that out of the way, let's start the application using this command:
encore run
You should see this:
πΉ Open the local development dashboard
While encore run
is running, open http://localhost:9400/ to view Encore's local developer dashboard.
Here you can see automatic API documentation, try your APIs with the API explorer, and study your application behavior using Encore's built-in distributed tracing.
π Call the API
Now that your app is running, you can ping the board.Create
endpoint to create a Trello board. Either from the API explorer in the local dev dashboard, or using cURL:
curl 'http://localhost:4000/board.Create' -d '{"Name":"my board"}'
π‘ Once you've called the API, you can view a trace of the request from the local dev dashboard.
π€ How it works: Defining API endpoints
Let's take a look at the code implementing the endpoint we just called:
// Create creates a new board.
//encore:api public
func Create(ctx context.Context, params *CreateParams) (*Board, error) {
b := &Board{Name: params.Name, Created: time.Now()}
err := sqldb.QueryRow(ctx, `
INSERT INTO board (name, created)
VALUES ($1, $2)
RETURNING id
`, params.Name, b.Created).Scan(&b.ID)
if err != nil {
return nil, fmt.Errorf("could not create board: %v", err)
}
return b, nil
}
π‘ With Encore you define API endpoints by adding the //encore:api
annotation to any normal Go function. At compile time, Encore then generates the boilerplate code necessary to turn this into a real API endpoint. (Learn more in the docs)
π€ How it works: Creating microservices
With Encore you create a microservice by defining one or more APIs within a regular Go package.
Encore recognizes this as a service, and uses the package name as the service name. When deploying, Encore will automatically provision the required infrastructure for each service.
On disk it might look like this:
/my-app
βββ encore.app // ... and other top-level project files
β
βββ hello // hello service (a Go package)
βΒ Β βββ hello.go // hello service code
βΒ Β βββ hello_test.go // tests for hello service
β
βββ world // world service (a Go package)
βββ world.go // world service code
π€ How it works: Making API calls
Calling an API endpoint looks like a regular function call with Encore. Import the service package as a regular Go package using import "encore.app/package-name"
and then call the API endpoint like a regular function. Encore will then automatically generate the necessary boilerplate at compile-time.
π‘In the example below, we import the service package hello
and call the Ping
endpoint using a function call to hello.Ping
.
import "encore.app/hello" // import service
//encore:api public
func MyOtherAPI(ctx context.Context) error {
resp, err := hello.Ping(ctx, &hello.PingParams{Name: "World"})
if err == nil {
log.Println(resp.Message) // "Hello, World!"
}
return err
}
π‘This means your development workflow is as simple as building a monolith, even if you use multiple services. You get all the benefits of function calls, like compile-time checking of all the parameters and auto-completion in your editor, while still allowing the division of code into logical components, services, and systems.
Then when compiling your application, Encore uses static analysis to parse all API calls and compiles them to proper API calls.
π Deploy to the cloud
Commit the new files to the project's git repo and trigger a deploy to Encore's free development cloud by running:
git add -A .
git commit -m 'Commit message'
git push encore
After triggering the deployment, you will see a URL where you can view its progress in Encore's Cloud Dashboard.π
It will look something like: https://app.encore.dev/$APP_ID/deploys/...
From there you can also see metrics, traces, and connect your own AWS or GCP account to use for production deployment.
π Great job - you're done!
You now have the start of a scalable Go microservices backend running in the cloud, complete with PostgreSQL databases.
Keep building with these Open Source App Templates.π
If you have questions or want to share your work, join the developers hangout in Encore's community on Discord.π
Posted on December 6, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.