
Backend development leans on repetitive boilerplate, especially around database queries and API servers. Writing that code by hand is slow and easy to get wrong. Codegen tools turn a declarative spec into type-safe Go code you do not maintain by hand.
This article walks through two tools:
sqlc reads your .sql files and emits Go functions that run those queries with typed parameters and results.
sqlc.yaml.sqlc generate.Say you have a users table:
-- schema.sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL
);And a query that looks up a user by email:
-- queries.sql
-- name: GetUserByEmail :one
SELECT id, name, email FROM users WHERE email = $1;The sqlc.yaml config:
version: "1"
packages:
- name: "db"
path: "./db"
queries: "./queries.sql"
schema: "./schema.sql"
engine: "postgresql"Run:
sqlc generatesqlc produces a method like:
func (q *Queries) GetUserByEmail(ctx context.Context, email string) (User, error)Use it in the backend:
user, err := dbQueries.GetUserByEmail(ctx, "alice@example.com")
if err != nil {
// handle error
}
fmt.Println("User:", user.Name)The manual SQL string handling and row scanning are gone. The code is cleaner and safer.
oapi-codegen reads an OpenAPI spec and emits Go server and client code. It builds the interfaces and the request and response types. You are left to implement the business logic.
oapi-codegen.Here is a small OpenAPI spec api.yaml:
openapi: 3.0.0
info:
title: User API
version: 1.0.0
paths:
/users/{email}:
get:
summary: Get user by email
parameters:
- name: email
in: path
required: true
schema:
type: string
responses:
"200":
description: User found
content:
application/json:
schema:
$ref: "#/components/schemas/User"
"404":
description: User not found
components:
schemas:
User:
type: object
properties:
id:
type: integer
name:
type: string
email:
type: stringGenerate the server code:
oapi-codegen -generate types,server -package api -o api.gen.go api.yamlThis produces request and response types such as the User struct, plus a ServerInterface with a method:
GetUsersEmail(ctx context.Context, email string) (api.User, error)Implement the interface:
type ServerImpl struct {
db *db.Queries
}
func (s *ServerImpl) GetUsersEmail(ctx context.Context, email string) (api.User, error) {
user, err := s.db.GetUserByEmail(ctx, email)
if err != nil {
return api.User{}, err
}
return api.User{
Id: int64(user.ID),
Name: user.Name,
Email: user.Email,
}, nil
}Wire up the server:
router := api.NewRouter(&ServerImpl{db: dbQueries})
http.ListenAndServe(":8080", router)sqlc and oapi-codegen together automate the database access and the API server code in a Go backend. You write less boilerplate and hit fewer runtime errors. The focus stays on the core logic of the application.
© Melvin Laplanche - All rights reserved.