A production-ready GraphQL API built with Go, gqlgen (schema-first), PostgreSQL (sqlx), JWT Authentication, and Dataloaders to solve N+1 problems.
- Getting Started Guide - Complete setup instructions and GraphQL basics
- GraphQL Examples - Comprehensive query/mutation examples, authentication, and client libraries
- Deployment Guide - Production deployment for Docker, AWS, Kubernetes, and serverless
- ✅ Schema-First GraphQL - Using gqlgen for type-safe GraphQL APIs
- ✅ Dataloader Pattern - Automatic N+1 query prevention with batching and caching
- ✅ JWT Authentication - Secure token-based authentication
- ✅ PostgreSQL - Robust relational database with migrations
- ✅ Clean Architecture - Resolver → Service → Repository layers
- ✅ GraphQL Playground - Built-in interactive API explorer
- ✅ Structured Logging - Using Go's native
slogpackage - ✅ Docker Ready - Multi-stage Dockerfile for production
- ✅ CI/CD - GitHub Actions for automated testing
- ✅ Testing - Unit tests with mocks
graph TD
Client([GraphQL Client])
subgraph API Server
MW["Middleware<br>Logging · JWT · Dataloader"]
GQ["GraphQL Handler<br>gqlgen"]
RES["Resolvers<br>graph/resolvers"]
SVC["Services<br>internal/services"]
REPO["Repositories<br>internal/repositories"]
end
DB[(PostgreSQL)]
Client -->|HTTP POST /query| MW
MW --> GQ
GQ --> RES
RES -->|Call| SVC
SVC -->|Call| REPO
REPO -->|Query| DB
Without Dataloaders (N+1 Problem):
query {
posts { # 1 query for posts
id
title
author { # N queries (one per post)
name
}
}
}
# Total: N+1 queriesWith Dataloaders (Optimized):
1 query for posts
1 batched query for all authors
Total: 2 queries ✅
- Go 1.22+
- PostgreSQL 15+
- Docker & Docker Compose (optional)
git clone https://github.com/ElioNeto/go-graphql-api-boilerplate.git
cd go-graphql-api-boilerplate
cp .env.example .env
# Edit .env with your settingsdocker-compose up -dThe GraphQL Playground will be available at http://localhost:8080/
# Install dependencies
make tidy
# Generate GraphQL code (important!)
make generate
# Create database
createdb graphql_db
# Run migrations
make migrate-up
# Start the server
make runOpen http://localhost:8080/ in your browser.
Register a user:
mutation {
createUser(input: {
name: "Alice"
email: "alice@example.com"
password: "SecurePass123!"
}) {
id
name
email
}
}Login:
mutation {
login(input: {
email: "alice@example.com"
password: "SecurePass123!"
}) {
token
user {
id
name
}
}
}Get current user (add token to HTTP Headers: Authorization: Bearer <token>):
query {
me {
id
name
email
}
}For more examples, see the GraphQL Examples guide.
go-graphql-api-boilerplate/
├── cmd/
│ └── api/
│ └── main.go # Entry point
├── graph/
│ ├── schema.graphqls # GraphQL schema definition
│ ├── schema.resolvers.go # Resolver implementations
│ ├── generated.go # Generated by gqlgen
│ └── model/ # Generated GraphQL models
├── internal/
│ ├── config/ # Environment configuration
│ ├── dataloaders/ # N+1 prevention with batching
│ ├── middleware/ # Auth, logging, dataloader injection
│ ├── models/ # Domain models
│ ├── repositories/ # Data access layer
│ └── services/ # Business logic
├── migrations/ # SQL migrations
├── docs/ # Documentation
│ ├── GETTING_STARTED.md
│ ├── GRAPHQL_EXAMPLES.md
│ └── DEPLOYMENT.md
├── .github/workflows/ # CI/CD
├── Dockerfile
├── docker-compose.yml
├── gqlgen.yml # gqlgen configuration
├── Makefile
└── README.md
The API is schema-first. Define your schema in graph/schema.graphqls, then generate code:
make generatetype User {
id: ID!
name: String!
email: String!
createdAt: Time!
updatedAt: Time!
}
input CreateUserInput {
name: String!
email: String!
password: String!
}
type Query {
me: User!
user(id: ID!): User
users: [User!]!
}
type Mutation {
createUser(input: CreateUserInput!): User!
login(input: LoginInput!): AuthPayload!
}# Run all tests
make test
# Run with coverage
make test-coverage
# Run specific package tests
go test ./internal/services/... -v# Run with docker-compose (includes PostgreSQL)
docker-compose up -d
# View logs
docker-compose logs -f api
# Stop services
docker-compose downThis boilerplate is production-ready and can be deployed to:
- Docker / Docker Compose - Container-based deployment
- AWS ECS / Fargate - Fully managed containers
- AWS App Runner - Easiest AWS deployment
- AWS Lambda + API Gateway - Serverless GraphQL
- Kubernetes - Scalable container orchestration
- Railway / Render - Simple PaaS deployment
Important for Production:
- Set
GRAPHQL_PLAYGROUND_ENABLED=false - Set
GRAPHQL_INTROSPECTION_ENABLED=false - Use strong JWT secrets
- Enable SSL for database connections
- Implement rate limiting
For detailed deployment instructions, check the Deployment Guide.
| Variable | Default | Required | Description |
|---|---|---|---|
APP_ENV |
development |
No | Environment name |
APP_PORT |
8080 |
No | Listen port |
APP_DEBUG |
false |
No | Enable debug logging |
DB_HOST |
– | Yes | PostgreSQL host |
DB_PORT |
5432 |
No | PostgreSQL port |
DB_USER |
– | Yes | Database user |
DB_PASSWORD |
– | Yes | Database password |
DB_NAME |
– | Yes | Database name |
AUTH_JWT_SECRET |
– | Yes | JWT signing secret |
GRAPHQL_PLAYGROUND_ENABLED |
true |
No | Enable GraphQL Playground |
GRAPHQL_INTROSPECTION_ENABLED |
true |
No | Enable schema introspection |
For a complete reference, see Getting Started Guide.
# Development
make run # Run the API
make generate # Generate GraphQL code (after schema changes)
make tidy # Tidy Go modules
# Database
make migrate-up # Apply migrations
make migrate-down # Rollback migrations
make migrate-create # Create new migration
# Testing
make test # Run tests
make test-coverage # Run tests with coverage
make lint # Run linters
# Building
make build # Build binary
make docker-build # Build Docker image
# Cleaning
make clean # Remove build artifactsResolvers connect GraphQL queries/mutations to your business logic:
func (r *queryResolver) User(ctx context.Context, id string) (*models.User, error) {
return r.UserService.GetByID(ctx, id)
}Services contain business logic:
func (s *UserService) GetByID(ctx context.Context, id string) (*models.User, error) {
return s.repo.GetByID(ctx, id)
}Repositories handle data access:
func (r *UserRepository) GetByID(ctx context.Context, id string) (*models.User, error) {
var user models.User
err := r.db.GetContext(ctx, &user, "SELECT * FROM users WHERE id = $1", id)
return &user, err
}Dataloaders batch and cache requests automatically:
// In resolver
func (r *postResolver) Author(ctx context.Context, obj *models.Post) (*models.User, error) {
return dataloaders.GetUserLoader(ctx).Load(obj.AuthorID)
}
// Dataloader batches all author IDs and fetches in one queryContributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Run tests (
make test) - Regenerate GraphQL code if schema changed (
make generate) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
MIT © Elio Neto
- Go REST API Boilerplate - REST version of this boilerplate
If you find this project helpful, please consider giving it a ⭐️!