GoTickets is a beginner-friendly REST API project written in Go. It shows how to build a small ticket booking backend with user authentication, events, bookings, PostgreSQL, GORM, Echo, validation, and JWT.
This project is useful for students who are learning how a Go web application is usually organized.
- How to create a web server with Echo
- How to organize code into packages
- How to connect Go with PostgreSQL using GORM
- How to create users and login with JWT authentication
- How to protect routes with middleware
- How to use DTOs for request and response data
- How service, repository, and handler layers work together
- Go
- Echo for HTTP routing
- GORM for database access
- PostgreSQL as the database
- JWT for authentication
- Validator for request validation
- godotenv for loading
.envfiles
gotickets/
├── cmd/
│ └── main.go # Application entry point
├── internal/
│ ├── auth/ # JWT token create/validate logic
│ ├── config/ # Environment and database config
│ ├── domain/
│ │ ├── booking/ # Booking feature
│ │ │ └── dto/ # Booking request/response DTOs
│ │ ├── event/ # Event feature
│ │ │ └── dto/ # Event request/response DTOs
│ │ └── user/ # User auth/profile feature
│ │ └── dto/ # User request/response DTOs
│ ├── httpresponse/ # Common error response shape
│ ├── middlewares/ # Auth middleware
│ └── server/ # Echo server setup
├── .air.toml # Air config for live reload
├── .env.example # Example environment variables
├── go.mod # Go module and dependencies
└── go.sum
For most features, the code follows this pattern:
Route -> Handler -> Service -> Repository -> Database
- Route decides the URL and HTTP method.
- Handler reads the request and returns the response.
- Service contains the main business logic.
- Repository talks to the database.
- DTO defines request and response data shapes.
Example:
POST /api/v1/bookings
-> booking handler
-> booking service
-> booking repository
-> PostgreSQL
Before running the project, install:
- Go
- PostgreSQL
- Git
You can check your Go version with:
go version- Clone the project:
git clone https://github.com/Apollo-Level2-Web-Dev/gotickets.git
cd gotickets- Create a PostgreSQL database:
CREATE DATABASE gotickets;- Create your
.envfile:
cp .env.example .env- Update
.envwith your own database values:
DSN="host=localhost user=postgres password=postgres dbname=gotickets port=5432 sslmode=disable TimeZone=Asia/Dhaka"
PORT=8080
JWT_SECRET=change-this-secretThe DSN value tells GORM how to connect to PostgreSQL.
Install dependencies:
go mod tidyStart the server:
go run cmd/main.goOr, if you are using Air for live reload:
airIf everything is okay, the server will start on:
http://localhost:8080
Check the health route:
curl http://localhost:8080/healthExpected response:
running
Create a local binary:
go build -o bin/gotickets ./cmd/main.goRun the binary:
./bin/goticketsFor a smaller production-style binary:
CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/gotickets ./cmd/main.goBefore building for production, it is a good habit to run:
go mod tidy
go test ./...
go vet ./...Register a new user:
POST /api/v1/auth/registerExample:
curl -X POST http://localhost:8080/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"name": "Mahi",
"email": "mahi@example.com",
"password": "secret123"
}'Login:
POST /api/v1/auth/loginExample:
curl -X POST http://localhost:8080/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "mahi@example.com",
"password": "secret123"
}'The login response returns a JWT token. Keep that token for protected routes.
Get current user:
GET /api/v1/auth/meExample:
curl http://localhost:8080/api/v1/auth/me \
-H "Authorization: Bearer YOUR_TOKEN_HERE"Create an event:
POST /api/v1/eventsExample:
curl -X POST http://localhost:8080/api/v1/events \
-H "Content-Type: application/json" \
-d '{
"title": "Go Workshop",
"description": "A beginner friendly Go learning event",
"location": "Dhaka",
"starts_at": "2026-07-01T10:00:00Z",
"total_tickets": 100,
"price": 500
}'Get all events:
GET /api/v1/eventsExample:
curl http://localhost:8080/api/v1/eventsGet one event:
GET /api/v1/events/:idExample:
curl http://localhost:8080/api/v1/events/1Update an event:
PATCH /api/v1/events/:idExample:
curl -X PATCH http://localhost:8080/api/v1/events/1 \
-H "Content-Type: application/json" \
-d '{
"title": "Updated Go Workshop",
"price": 700
}'Booking routes are protected. You must login first and send the JWT token in the Authorization header.
Create a booking:
POST /api/v1/bookingsExample:
curl -X POST http://localhost:8080/api/v1/bookings \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN_HERE" \
-d '{
"event_id": 1,
"quantity": 2
}'Get my bookings:
GET /api/v1/bookings/meExample:
curl http://localhost:8080/api/v1/bookings/me \
-H "Authorization: Bearer YOUR_TOKEN_HERE"This is where the application starts. It loads environment variables, connects to the database, and starts the server.
This package reads values from .env and creates the database connection.
This package creates the Echo app, adds middleware, registers routes, and starts listening on the selected port.
This folder contains the main business domains: user, event, and booking. Each domain has its own handler, service, repository, entity, route registration, and DTOs.
A handler receives an HTTP request. It usually does these things:
- Reads JSON input
- Validates the input
- Calls the service
- Sends JSON response
A service contains business logic. For example, the booking service checks if enough tickets are available before creating a booking.
A repository is responsible for database queries. This keeps database code separate from business logic.
Middleware runs before the handler. In this project, auth middleware checks the JWT token and adds user information to the request context.
The project uses GORM AutoMigrate, so tables are created automatically when the server starts.
Current main tables:
- users
- events
- bookings
Make sure you created a .env file:
cp .env.example .envCheck these things:
- PostgreSQL is running
- Database name is correct
- Username and password are correct
- Port is correct, usually
5432
Make sure the request has this header:
Authorization: Bearer YOUR_TOKEN_HERE
Also make sure the token comes from the login route.
If you are new to Go, read the project in this order:
cmd/main.gointernal/config/config.gointernal/config/db.gointernal/server/http.gointernal/domain/user/register.gointernal/domain/user/handler.gointernal/domain/user/service.gointernal/domain/user/repository.gointernal/domain/eventinternal/domain/booking
This order helps you understand how the app starts, then how one feature works from route to database.