A RESTful Blog API built with Go, Fiber v3, GORM, and PostgreSQL.
The project is organized using a layered architecture to keep HTTP handlers, business logic, database access, and data models separated.
- User registration and login
- JWT-based authentication
- Access and refresh tokens
- Password hashing with bcrypt
- Create, update, read, and delete blog posts
- Blog ownership checks
- Blog likes
- Comments on blog posts
- Update and delete comments with ownership checks
- Pagination for comments
- PostgreSQL persistence with GORM
- Automatic database migrations
- Environment-based configuration
- Layered architecture using handlers, services, and repositories
| Technology | Purpose |
|---|---|
| Go | Backend language |
| Fiber v3 | HTTP web framework |
| GORM | ORM |
| PostgreSQL | Database |
| JWT | Authentication |
| bcrypt | Password hashing |
| UUID | Entity identifiers |
| godotenv | Environment configuration |
The API follows a simple layered architecture:
Client
│
▼
Routes
│
▼
Handlers
│
▼
Services
│
▼
Repositories
│
▼
PostgreSQL
.
├── cmd/
│ └── main.go
│
├── internal/
│ ├── Auth/
│ │ └── Auth.go
│ │
│ ├── database/
│ │ └── connection.go
│ │
│ ├── dto/
│ │ └── dto.go
│ │
│ ├── handlers/
│ │ ├── blogHandlers.go
│ │ ├── commenthandler.go
│ │ ├── likeHandler.go
│ │ └── userHandler.go
│ │
│ ├── middleware/
│ │ └── middleware.go
│ │
│ ├── models/
│ │ └── models.go
│ │
│ ├── repository/
│ │ ├── blogrepo.go
│ │ ├── commentrepo.go
│ │ ├── Likerepo.go
│ │ └── userrepo.go
│ │
│ ├── routes/
│ │ └── routes.go
│ │
│ └── service/
│ ├── blogservice.go
│ ├── commentservice.go
│ ├── LikeService.go
│ └── Userservice.go
│
├── .env
├── .gitignore
├── go.mod
├── go.sum
├── makefile
└── README.md
The API currently contains four main entities:
idusernameemailpasswordbioavatar_url- timestamps
Passwords are stored as bcrypt hashes and are never returned in JSON responses.
idtitlecontentcategoryuser_idlike_num- timestamps
A blog belongs to a user and can have comments and likes.
idcontentuser_idblog_id- timestamps
Comments belong to both a user and a blog.
iduser_idblog_id
A like associates a user with a blog.
Authentication uses JWT.
When a user registers or logs in, the API returns:
{
"access_token": "ACCESS_TOKEN",
"refresh_token": "REFRESH_TOKEN"
}The access token is valid for 15 minutes and the refresh token is valid for 7 days.
Protected routes require:
Authorization: Bearer <access_token>The middleware validates the token and stores the authenticated user's ID in the Fiber context.
Base URL:
http://localhost:3000
POST /registerRequest:
{
"UserName": "moncef",
"email": "moncef@example.com",
"password": "password123",
"Bio": "Backend developer"
}Response:
{
"access_token": "ACCESS_TOKEN",
"refresh_token": "REFRESH_TOKEN"
}POST /loginRequest:
{
"Email": "moncef@example.com",
"Password": "password123"
}GET /blogs/:idExample:
GET /blogs/550e8400-e29b-41d4-a716-446655440000
Authentication required
POST /blogs
Authorization: Bearer <access_token>Request:
{
"Title": "My first blog",
"Content": "This is my first blog post.",
"Category": "Programming"
}Authentication required
PUT /blogs/:id
Authorization: Bearer <access_token>Request:
{
"title": "Updated title",
"content": "Updated content.",
"password": "password123"
}The current implementation verifies the user's password and ensures that the authenticated user owns the blog before updating it.
Authentication required
DELETE /blogs/:id
Authorization: Bearer <access_token>Request:
{
"Password": "password123"
}The API verifies both the user's password and blog ownership before deletion.
Authentication required
POST /like
Authorization: Bearer <access_token>Request:
{
"UserID": "USER_UUID",
"BlogID": "BLOG_UUID"
}DELETE /like
Authorization: Bearer <access_token>Request:
{
"ID": "LIKE_UUID",
"UserID": "USER_UUID",
"BlogID": "BLOG_UUID"
}POST /posts/:postID/comments/
Authorization: Bearer <access_token>Request:
{
"content": "Great post!"
}GET /posts/:postID/comments/?page=1&limit=10Example:
GET /posts/BLOG_UUID/comments/?page=1&limit=10
Response:
{
"comments": [
{
"id": "COMMENT_UUID",
"content": "Great post!",
"user_id": "USER_UUID",
"blog_id": "BLOG_UUID",
"created_at": "2026-08-10T10:00:00Z",
"updated_at": "2026-08-10T10:00:00Z"
}
],
"meta": {
"page": 1,
"limit": 10,
"total_items": 1,
"total_pages": 1
}
}Pagination defaults to:
page = 1
limit = 10
The maximum comment page size is 50.
GET /comments/:commentIDAuthentication required
PUT /comments/:commentID
Authorization: Bearer <access_token>Request:
{
"content": "Updated comment"
}Only the owner of the comment can update it.
Authentication required
DELETE /comments/:commentID
Authorization: Bearer <access_token>Only the owner of the comment can delete it.
GET /users/:userID/comments/?page=1&limit=10Create a .env file in the project root:
JWT_SECRET=your-strong-secret
DSN=host=localhost user=postgres password=your-password dbname=Blogdb port=5432 sslmode=disableDo not commit your real .env file or JWT secret to Git.
The application reads:
JWT_SECRET— secret used to sign and validate JWTsDSN— PostgreSQL connection string
Make sure PostgreSQL is installed and running.
Create the database:
CREATE DATABASE Blogdb;You do not need to manually create the tables.
On startup, GORM runs:
db.AutoMigrate(
&models.User{},
&models.Blog{},
&models.Comment{},
&models.Like{},
)and creates/updates the required tables.
git clone https://github.com/moncef-an/Blog-API.git
cd Blog-APICreate .env:
JWT_SECRET=your-strong-secret
DSN=host=localhost user=postgres password=your-password dbname=Blogdb port=5432 sslmode=disablego mod downloadgo run cmd/main.goThe server starts on:
http://localhost:3000
The project includes a Makefile.
Run the application:
make runBuild the application:
make buildClean the generated binary:
make cleancurl -X POST http://localhost:3000/register \
-H "Content-Type: application/json" \
-d '{
"UserName": "moncef",
"email": "moncef@example.com",
"password": "password123",
"Bio": "Backend developer"
}'curl -X POST http://localhost:3000/login \
-H "Content-Type: application/json" \
-d '{
"Email": "moncef@example.com",
"Password": "password123"
}'Then use the returned access token:
curl http://localhost:3000/blogs/BLOG_UUID \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"Repositories are responsible for database operations.
For example:
BlogHandler
↓
BlogService
↓
BlogRepository
↓
GORM
↓
PostgreSQL
This keeps database logic out of the HTTP handlers.
The service layer contains business rules such as:
- verifying blog ownership
- verifying passwords
- validating comments
- handling pagination
- managing likes
- creating authentication tokens
Authentication is handled through Fiber middleware rather than repeating JWT validation inside every protected handler.
The project currently implements:
- bcrypt password hashing
- JWT authentication
- access/refresh token separation
- protected routes
- ownership checks for blog and comment modifications
- environment variables for secrets and database credentials
For production, additional protections should be considered, such as:
- refresh-token rotation/revocation
- rate limiting
- request validation
- stricter CORS configuration
- centralized error handling
- database constraints for preventing duplicate likes
- HTTPS
- structured logging
The project is still evolving. Some useful next improvements would be:
- Add a refresh-token endpoint
- Add an endpoint to list all blogs
- Add unique constraints for
(user_id, blog_id)likes - Prevent a user from liking the same blog multiple times
- Improve request validation
- Add Swagger/OpenAPI documentation
- Add automated unit and integration tests
- Add Docker and Docker Compose
- Add pagination for blogs
- Add centralized error handling
- Improve authentication context consistency across handlers
- Add CI/CD with GitHub Actions
This project was built to practice backend development concepts in Go, including:
- REST API design
- Fiber
- PostgreSQL
- GORM
- JWT authentication
- bcrypt
- middleware
- DTOs
- repository pattern
- service layer
- dependency injection
- pagination
- relational data modeling
This project is intended for learning and portfolio purposes.