Building a Go API for My Portfolio

Views: β€”

Recently, I just felt an urge to do something you are not supposed to do. I thought, why not add interactive elements to my supposedly completely static page. So despite this article being written in mdx, it interacts with my API via TS!

Why Go?

Go has been on my radar for a while now. It’s fast, simple, and perfect for building small web services. Plus I thought it’d be a nice oppurtunity to learn how to create a docker image and containerize my api on my server.

Also, before writing this api I created a different project in Go called docky-go. More on that in a different blog post.

The Tech Stack

Libs used for this project are chosen on a rule-of-cool basis. Basically I picked the most popular kid on the block; so we have:

  • Gin for HTTP web framework
  • GORM for the database management
  • PostgreSQL 15+ as the actual database (this I use quite frequently as of late)

I also used:

  • JWT for auth
  • Docker for deployment

Project Structure

I organized the API following a clean architecture pattern:

api/
β”œβ”€β”€ controllers/
β”œβ”€β”€ models/
β”œβ”€β”€ middleware/
β”œβ”€β”€ routes/
β”œβ”€β”€ config/
└── main.go

Authentication System

I rigged it with explosives so do not even try to jailbreak it.

Register Endpoint

POST /api/v1/auth/register

{
  "username": "user123",
  "email": "user@example.com",
  "password": "securepassword"
}

Passwords are hashed using bcrypt before storage. Users register with the β€œUser” role by default. Admin access requires database-level promotion.

Login & Logout

POST /api/v1/auth/login

{
  "email": "user@example.com",
  "password": "securepassword"
}

POST /api/v1/auth/logout
Headers: Authorization: Bearer {token}

On successful login, the API returns a JWT token with 7-day expiry:

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "user": {
    "id": 1,
    "username": "user123",
    "email": "user@example.com",
    "role": 0,
    "created_at": "2025-12-13T22:08:56Z"
  }
}

Logout revokes the token by adding it to a blacklist, preventing reuse even before expiry.

Voting System

The voting system allows authenticated users to like or dislike articles. Each user can only vote once per article, but they can change their vote.

Vote Endpoints

Submit a vote: POST /api/v1/votes

{
  "article_id": 1,
  "vote_type": "like"
}

Remove a vote: DELETE /api/v1/votes/:article_id

Get vote counts: GET /api/v1/votes/:article_id

// Response:
{
  "article_id": 1",
  "likes": 42,
  "dislikes": 3,
  "user_has_voted": true
}

Security and auth

What we have so far:

  • Bcrypt password hashing
  • JWT token blacklist for logout
  • Role-based access control (User/Admin)
  • Rate limiting (100 req/s, burst 200)
  • CORS configuration for allowed origins
  • Security headers (XSS, HSTS, etc.)
  • SQL injection protection via GORM parameterized queries
  • Configurable Request size limits

Deployment

The API is containerized with Docker and deployed on my self-hosted server behind Traefik. The entire deployment is managed with Docker Compose:

services:
  # PatWos - Api: my api for my portfolio
  patwos-api:
    container_name: patwos-api
    image: ghcr.io/wosiu6/patwos-api:latest
    profiles: ["apps", "all"]
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    networks:
      - default
      - t3_proxy
    ports:
      - "${PATWOS_API_PORT}:8080"
    depends_on:
      - patwos-api-db
    healthcheck:
      disable: false
    volumes:
      - ./data/logs:/app/logs
      - ./data/uploads:/app/uploads
      - ./data/cache:/app/cache
    environment:
      DB_PASSWORD: ${PATWOS_API_DB_PASSWORD}
      DB_HOST: ${PATWOS_API_DB_HOST}
      DB_USER: ${PATWOS_API_DB_USER}
      DB_NAME: ${PATWOS_API_DB_NAME}
      API_PORT: ${PATWOS_API_PORT}
      GIN_MODE: release
      ALLOWED_ORIGINS: ${PATWOS_API_ALLOWED_ORIGINS}
      MAX_REQUEST_SIZE: ${PATWOS_API_MAX_REQUEST_SIZE}
      JWT_SECRET: ${PATWOS_API_JWT_SECRET}

What’s Next?

Future Improvements

  • Addin capcha or maybe email verification
  • Comment reply threading
  • Analytics dashboard for vote and comment statistics
  • Admin dashboard perhaps?

Conclusion

Whilst it still needs some work, I am glad to see it up and running and come together like this.

Final Thoughts

Want to try it? Register an account and vote on articles! The API is live and ready to use.

Remember - no blood, no trabajo

β€”