A complete, production-ready REST API service built with GoServe framework, PostgreSQL, Redis, JWT authentication, and role-based authorization.
This project is a fully production-ready blog service demonstrating best practices for building performant and secure backend REST API services with PostgreSQL. It showcases the application of the GoServe framework with clean architecture, feature separation, comprehensive testing, and production grade security.
- GoServe Framework - Built on the production-ready GoServe v2 framework
- Clean Architecture - Well-structured, maintainable codebase with clear separation of concerns
- PostgreSQL Integration - Full PostgreSQL support with migrations and type-safe queries
- Redis Caching - High-performance caching layer for frequently accessed data
- JWT Authentication - Secure token-based authentication with refresh tokens
- Role-Based Authorization - Fine-grained access control with role management
- API Key Support - Additional security layer for API access control
- Request Validation - Comprehensive input validation using validator v10
- Testing Suite - Extensive unit and integration test coverage
- Docker Ready - Complete Docker Compose setup for easy deployment
- Auto-Generated APIs - CLI tool for scaffolding new API endpoints
- Type-Safe DTOs - Structured data transfer objects for all requests/responses
- Language: Go 1.21+
- Framework: GoServe v2
- Web Framework: Gin
- Database: PostgreSQL (pgx)
- Migrations: golang-migrate
- Cache: Redis (go-redis)
- Authentication: JWT tokens
- Validation: validator
- Configuration: Viper
- Testing: Testify
- Docker & Docker Compose (Installation Guide)
- Go 1.21+ (for local development)
1. Clone the Repository
git clone https://github.com/afteracademy/goserve-example-api-server-postgres.git
cd goserve-example-api-server-postgres2. Generate RSA Keys
go run .tools/rsa/keygen.go3. Create Environment Files
go run .tools/copy/envs.go 4. Start with Docker Compose
docker compose up --buildThe API server will be available at: http://localhost:8080
5. Health Check
docker inspect --format='{{.State.Health.Status}}' goserver-postgres6. Run Tests
docker exec -t goserver-postgres go test -v ./...If you encounter issues:
- Ensure port 8080 is available (change
SERVER_PORTin.envif needed) - Ensure port 5432 is available (change
DB_PORTin.envif needed) - Ensure port 6379 is available (change
REDIS_PORTin.envif needed)
For local development without Docker:
go mod tidyKeep Docker containers for postgres and redis running, but stop the goserver-postgres container.
Update the following in .env and .test.env:
DB_HOST=localhost
REDIS_HOST=localhostRun the application:
go run cmd/main.goOr use VS Code: Use the Run and Debug panel for an enhanced development experience.
The architecture is designed to make each API independent while sharing services among them. This promotes:
- Code Reusability - Shared services across multiple endpoints
- Team Collaboration - Reduced conflicts when working in teams
- Feature Isolation - Easier testing and maintenance
Startup Flow:
cmd/main → startup/server → module, postgres, redis, router → api/[feature]/middlewares → api/[feature]/controller → api/[feature]/service → authentication, authorization → handlers → response
Sample API
├── dto/
│ └── create_sample.go # Data Transfer Objects
├── model/
│ └── sample.go # PostgreSQL Table Model
├── middleware/ # (Optional) Feature-specific middleware
│ └── custom.go
├── controller.go # Route definitions & handlers
└── service.go # Business logic & data operations
Key Components:
- DTOs - Request/response body definitions in
dto/directory - Models - PostgreSQL table models in
model/directory - Controller - Defines endpoints and handles HTTP requests
- Service - Contains business logic and data operations
- Middleware - Authentication, authorization, and custom middleware
| Directory | Purpose |
|---|---|
| api/ | Feature-based API implementations |
| cmd/ | Application entry point (main.go) |
| common/ | Shared code across all APIs |
| config/ | Environment variable configuration |
| keys/ | RSA keys for JWT token signing |
| migrations/ | PostgreSQL database migration files |
| startup/ | Server initialization, DB, Redis, routing |
| tests/ | Integration test suites |
| utils/ | Utility functions |
Helper Directories:
- .extra/ - PostgreSQL initialization scripts, assets, documentation
- .github/ - CI/CD workflows
- .tools/ - Code generators, key generation utilities
- .vscode/ - Editor configuration and debug settings
Scaffold a new API endpoint with a single command:
go run .tools/apigen.go sampleThis creates the complete structure under api/sample/ with:
- Model definitions
- DTO templates
- Controller skeleton
- Service interface
api/sample/model/sample.go
package model
import (
"time"
"github.com/google/uuid"
)
type Sample struct {
ID uuid.UUID // id
Field string // field
Status bool // status
CreatedAt time.Time // created_at
UpdatedAt time.Time // updated_at
}api/sample/dto/create_sample.go
package dto
import (
"time"
"github.com/google/uuid"
)
type InfoSample struct {
ID uuid.UUID `json:"_id" binding:"required"`
Field string `json:"field" binding:"required"`
CreatedAt time.Time `json:"createdAt" binding:"required"`
}api/sample/service.go
package sample
import (
"context"
"github.com/afteracademy/goserve-example-api-server-postgres/api/sample/dto"
"github.com/afteracademy/goserve-example-api-server-postgres/api/sample/model"
"github.com/afteracademy/goserve/v2/redis"
"github.com/afteracademy/goserve/v2/postgres"
"github.com/google/uuid"
)
type Service interface {
FindSample(id uuid.UUID) (*model.Sample, error)
}
type service struct {
db postgres.Database
infoSampleCache redis.Cache[dto.InfoSample]
}
func NewService(db postgres.Database, store redis.Store) Service {
return &service{
db: db,
infoSampleCache: redis.NewCache[dto.InfoSample](store),
}
}
func (s *service) FindSample(id uuid.UUID) (*model.Sample, error) {
ctx := context.Background()
query := `
SELECT
id,
field,
status,
created_at,
updated_at
FROM samples
WHERE id = $1
`
var m model.Sample
err := s.db.QueryRow(ctx, query, id).
Scan(
&m.ID,
&m.Field,
&m.Status,
&m.CreatedAt,
&m.UpdatedAt,
)
if err != nil {
return nil, err
}
return &m, nil
}github.com/afteracademy/goserve/v2/network/interfaces.go
type BaseService interface {
Context() context.Context
}- Redis Cache:
redis.Cache[dto.InfoSample]provide the methods to make common redis queries for the DTOdto.InfoSample
api/sample/controller.go
package sample
import (
"github.com/afteracademy/goserve-example-api-server-postgres/api/sample/dto"
"github.com/afteracademy/goserve-example-api-server-postgres/common"
coredto "github.com/afteracademy/goserve/v2/dto"
"github.com/afteracademy/goserve/v2/network"
"github.com/afteracademy/goserve/v2/utility"
"github.com/gin-gonic/gin"
)
type controller struct {
network.Controller
common.ContextPayload
service Service
}
func NewController(
authMFunc network.AuthenticationProvider,
authorizeMFunc network.AuthorizationProvider,
service Service,
) network.Controller {
return &controller{
Controller: network.NewController("/sample", authMFunc, authorizeMFunc),
ContextPayload: common.NewContextPayload(),
service: service,
}
}
func (c *controller) MountRoutes(group *gin.RouterGroup) {
group.GET("/id/:id", c.getSampleHandler)
}
func (c *controller) getSampleHandler(ctx *gin.Context) {
uuidParam, err := network.ReqParams[coredto.UUID](ctx)
if err != nil {
c.Send(ctx).BadRequestError(err.Error(), err)
return
}
sample, err := c.service.FindSample(uuidParam.ID)
if err != nil {
c.Send(ctx).NotFoundError("sample not found", err)
return
}
data, err := utility.MapTo[dto.InfoSample](sample)
if err != nil {
c.Send(ctx).InternalServerError("something went wrong", err)
return
}
c.Send(ctx).SuccessDataResponse("success", data)
}Controller Interface: Implements github.com/afteracademy/goserve/v2/network.Controller
type Controller interface {
BaseController
MountRoutes(group *gin.RouterGroup)
}
type BaseController interface {
ResponseSender
Path() string
Authentication() gin.HandlerFunc
Authorization(role string) gin.HandlerFunc
}startup/module.go
import (
...
"github.com/afteracademy/goserve-example-api-server-postgres/api/sample"
)
...
func (m *module) Controllers() []network.Controller {
return []network.Controller{
...
sample.NewController(m.AuthenticationProvider(), m.AuthorizationProvider(), sample.NewService(m.DB, m.Store)),
}
}Explore other GoServe example implementations:
-
GoServe Framework
Core framework with PostgreSQL, MongoDB, Redis, and NATS support -
MongoDB API Server
Complete REST API with MongoDB and clean architecture -
Microservices Example
NATS-based microservices communication patterns
Use the GoServeGen CLI to generate a starter project:
# Install GoServeGen CLI
go install github.com/afteracademy/goservegen@latest
# Generate a new project
goservegen create my-project --db=postgresOr download the starter project directly:
- How to Architect Good Go Backend REST API Services
- How to Create Microservices — A Practical Guide Using Go
- Implement JSON Web Token (JWT) Authentication using AccessToken and RefreshToken
We welcome contributions! Please feel free to:
- Fork the repository
- Open issues for bugs or feature requests
- Submit pull requests with improvements
- Share your feedback and suggestions
Subscribe to AfterAcademy on YouTube for in-depth tutorials and concept explanations:
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
If you find this project useful, please consider:
- Starring ⭐ this repository
- Sharing with the community
- Contributing improvements
- Reporting bugs and issues
Built with love by AfterAcademy
