---
title: Gin with PostgreSQL and GORM
description: Create a Go API project with Gin, PostgreSQL, GORM, Zap logging, and a clean Better Fullstack project structure.
updated: 2026-05-12
category: Go
tags:
  - gin
  - postgres
  - gorm
  - go
keywords:
  - gin postgres starter
  - go api boilerplate
  - gin gorm starter
  - create go api project
  - go postgres api
---

Use this stack when you want a Go HTTP API with a familiar router, relational database access, and pragmatic project structure.

```npm
npm create better-fullstack@latest my-go-api -- \
  --ecosystem go \
  --go-web-framework gin \
  --database postgres \
  --go-orm gorm \
  --go-logging zap
```

<GuideStackSnapshot
  title="Generated stack snapshot"
  description="The generated Go service is organized around these responsibilities."
  items={[
    { label: "HTTP", value: "Gin", detail: "Routing, request binding, middleware, and responses." },
    { label: "Data", value: "PostgreSQL + GORM", detail: "Model mapping and persistence for CRUD-heavy services." },
    { label: "Logging", value: "Zap", detail: "Structured logs that can carry request and dependency context." },
    { label: "Shape", value: "cmd + internal", detail: "A backend service layout that keeps app code behind package boundaries." },
  ]}
/>

## What this creates

- A Go API project using Gin.
- PostgreSQL as the database option.
- GORM for database access.
- Zap for structured logging.
- A generated project layout ready for backend development.

## Generated shape

This stack creates a conventional Go HTTP API. Gin handles routing and middleware, GORM owns model mapping and persistence, PostgreSQL is the backing store, and Zap provides structured logs.

The cleanest generated-app boundary is:

- HTTP handlers parse input and return responses.
- Services own business decisions.
- GORM models describe persisted records.
- Database setup and logger setup happen once during startup.

## Representative file tree

```txt
my-go-api/
  go.mod
  .env.example
  cmd/
    server/
      main.go
  internal/
    config/
      config.go
    database/
      postgres.go
    handlers/
      health.go
      users.go
    models/
      user.go
    services/
      users.go
```

Generated Go projects vary by option set, but this is the shape to preserve as the app grows: keep framework code near handlers and persistence code behind services or repositories.

## Handler and model examples

A Gin handler should bind request data, call application code, and return a clear HTTP status:

```go
package handlers

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

type CreateUserRequest struct {
	Email string `json:"email" binding:"required,email"`
	Name  string `json:"name" binding:"required"`
}

func (h *UserHandler) Create(c *gin.Context) {
	var input CreateUserRequest
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	user, err := h.Users.Create(c.Request.Context(), input.Email, input.Name)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "create user"})
		return
	}

	c.JSON(http.StatusCreated, user)
}
```

The matching GORM model can stay small and explicit:

```go
package models

import "time"

type User struct {
	ID        uint      `json:"id" gorm:"primaryKey"`
	Email     string    `json:"email" gorm:"uniqueIndex;not null"`
	Name      string    `json:"name" gorm:"not null"`
	CreatedAt time.Time `json:"createdAt"`
	UpdatedAt time.Time `json:"updatedAt"`
}
```

Route registration should keep related endpoints together:

```go
func RegisterRoutes(router *gin.Engine, users *UserHandler) {
	router.GET("/health", Health)

	api := router.Group("/api")
	api.POST("/users", users.Create)
	api.GET("/users/:id", users.Get)
}
```

## Compatibility notes

<GuideCompatibilityNote kind="supported" title="Compatibility rule">
  Keep `--go-web-framework gin`, `--database postgres`, and `--go-orm gorm` together for this guide's flow. If you change `--go-orm sqlc`, expect SQL files and generated query types instead of GORM models. If you set `--go-web-framework none`, this becomes a non-server project and the Gin handler examples no longer apply.
</GuideCompatibilityNote>

## When to choose it

Choose Gin when you want a direct Go API with common middleware patterns and low framework overhead.

## Tradeoffs

Gin and GORM are familiar choices for many Go teams. If you prefer a smaller router surface or different API style, compare the other Go framework options in the builder.

GORM is productive for CRUD-heavy applications, but it can hide SQL details. For query-heavy services where exact SQL is part of the contract, compare the Echo with SQLC guide.

## Testing and deployment notes

Use `httptest` for handler tests and a real PostgreSQL database for integration tests around GORM behavior.

```bash
go test ./...
```

Before deployment, confirm the generated app reads the database URL from the environment and that migrations or auto-migration behavior match your team's release policy. Use Zap fields for request IDs, user IDs, and dependency failures so production logs can be filtered without string parsing.

## Troubleshooting

- If requests return 400, inspect Gin binding tags before debugging service code.
- If inserts fail with duplicate key errors, check GORM indexes and PostgreSQL constraints together.
- If the app cannot connect to PostgreSQL, verify host, port, SSL mode, and the generated environment variable names.
- If logs are unstructured, make sure the Zap logger is passed into handlers and services instead of using `fmt.Println`.

## Next steps

- Open the [Stack Builder](/new?eco=go&gwf=gin&db=postgres&gorm=gorm&glog=zap).
- Read the [Go ecosystem docs](/docs/ecosystems/#go).
- Review the [CLI create reference](/docs/cli/create/).
