A starter Go backend project demonstrating CRUD operations with authentication, file uploads, and security middleware. Built with Gin, GORM, and MariaDB.
Documentation generated by Claude.
- Go with Gin — HTTP router
- GORM — ORM for database access
- MariaDB — relational database
- JWT — authentication tokens
- Swagger — auto-generated API documentation
1. Configure environment variables
Create a .env file at the project root:
JWT_SECRET=a-very-long-string-that-satisfies-256bits-requirementsThe MariaDB credentials are set via mariadb.env (already included):
MYSQL_ROOT_PASSWORD=root
MYSQL_DATABASE=mydb
MYSQL_USER=myuser
MYSQL_PASSWORD=mypassword2. Start the database
docker-compose up -dMariaDB runs on port 3306. The schema in init/001_schema.sql runs automatically on first start. Data is persisted in ./data/mysql/.
3. Run the server
go run main.goThe API will be available at http://localhost:8080.
Swagger UI is served at
http://localhost:8080/swagger/index.html.
Tests use an in-memory SQLite database — no running database required.
go test ./tests/...All routes except /users/register and /users/login require a valid JWT token in the Authorization header:
Authorization: Bearer <token>
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /users/register |
No | Create a new account |
| POST | /users/login |
No | Login and receive a JWT |
Register
POST /users/register
{
"email": "you@example.com",
"password": "Secret123!"
}Password requirements: minimum 8 characters, at least one uppercase letter, one lowercase letter, one digit, and one special character (! @ # % $ ^ & * .).
Login
POST /users/login
{
"email": "you@example.com",
"password": "Secret123!"
}Returns a JWT token valid for 2 hours.
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /projects/ |
Yes | List all projects |
| GET | /projects/:id |
Yes | Get one project |
| POST | /projects/ |
Yes | Create a project |
| PUT | /projects/:id |
Yes | Update a project |
| DELETE | /projects/:id |
Yes | Delete a project |
| PUT | /projects/:id/like |
Yes | Toggle like on a project |
Create a project — supports optional image upload via multipart/form-data
POST /projects/
{
"name": "My Project",
"description": "A cool project",
"skills": ["Go", "Docker", "SQL"]
}Or with an image:
curl -X POST http://localhost:8080/projects/ \
-H "Authorization: Bearer <token>" \
-F "name=My Project" \
-F "description=A cool project" \
-F 'skills=["Go","Docker"]' \
-F "image=@/path/to/image.png"Uploaded images are automatically resized to 800px width (aspect ratio preserved) and saved to ./uploads/.
Update a project — partial update, only send fields you want to change
PUT /projects/1
{
"name": "Updated name"
}Toggle like — sends no body; adds the like if not present, removes it if already liked.
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /comments |
Yes | Post a comment |
POST /comments
{
"project_id": 1,
"content": "Great project!"
}The user_id is set automatically from the JWT token — no need to send it in the body.
.
├── main.go # Entry point — wires middleware, routes, and database
├── go.mod # Module definition and dependencies
├── .env # JWT secret (not committed)
├── mariadb.env # MariaDB credentials for Docker
├── docker-compose.yml # MariaDB service
├── config/
│ ├── db.go # GORM database connection
│ ├── cors.go # CORS policy (localhost only)
│ ├── secure.go # Security headers (XSS, CSP, frame denial)
│ └── rate_limit.go # Global rate limiter (100 req/s)
├── models/
│ ├── user_model.go # User schema with bcrypt password
│ ├── project_model.go # Project schema with skills (JSON) and likes (M2M)
│ └── comment_model.go # Comment schema linked to User and Project
├── controllers/
│ ├── user_controller.go # Register, Login
│ ├── project_controller.go# CRUD + image upload + like toggle
│ └── comment_controller.go# Post comment
├── routes/
│ ├── user_routes.go # /users group
│ ├── project_routes.go # /projects group
│ └── comment_routes.go # /comments group
├── middlewares/
│ └── auth.go # JWT validation middleware
├── utils/
│ └── password_validator.go# Password strength rules
├── tests/
│ └── project_test.go # Endpoint tests (SQLite in-memory)
├── docs/
│ ├── docs.go # Generated Swagger spec
│ └── swagger.yaml # OpenAPI definition
└── init/
└── 001_schema.sql # DB init script (runs on first Docker start)
All routes pass through three global middleware layers applied at startup:
| Middleware | Details |
|---|---|
| Security | Sets X-Frame-Options: DENY, X-XSS-Protection, and Content-Security-Policy: default-src 'self' |
| CORS | Allows http://localhost with credentials; 12-hour preflight cache |
| Rate limiter | 100 requests/second global limit; returns 429 on violation |
Protected routes additionally run the JWT auth middleware, which reads the Authorization: Bearer <token> header, verifies the signature, and injects UserID into the request context.