Skip to content

minhhoccode111/realworld-fiber-clean

Repository files navigation

Realworld Fiber Clean Architecture

Golang codebase containing real world examples (CRUD, auth, advanced patterns, etc) that adheres to the RealWorld spec and API.

Learned Concepts

  • Go + Fiber
  • Clean Architecture
  • Dependency Injection
  • Embedded struct like Pagination and Timestamps in other struct
  • Generate mocks with mockgen and go generate ./...
  • Build Tags like '-tags migrate'
  • Struct tags for input validation in pkg/validatorx/validator.go
  • Struct tags for env vars loading in config/config.go
  • Swagger support in Golang project
  • DB Migration with migrate
  • squirrel for query builder
  • lvh.me domain
  • Secure: true cookie flag works with localhost but not something like http://fe.lvh.me because we need https at that point
  • Go debugger with .vscode/launch.json
  • In repo layer, we don't return pgx.ErrNoRows to outer layer to handle because it will make the router layer depend on postgresql implementation, instead we return our custom not found error entity.ErrNoRows
  • Generate TypeScript API types
  • A response struct with pointer fields, shouldn't use omitempty because handling profile?: ProfilePreview | null; is harder than profile: ProfilePreview | null; in TypeScript
  • Postgres 18 needs db_data:/var/lib/postgresql and not db_data:/var/lib/postgresql/data
  • To allow nginx service to read mounted host files, we need to run chmod -R 755 ./frontend/dist
  • jwt-in-cookie is more secure than jwt-in-header but is restricted by browser cookie rules. It works for same-site or explicitly allowed cross-site setups (SameSite=None + HTTPS), and only for domains you control. It cannot support arbitrary frontend domains
  • Fix the Docker and UFW security flaw without disabling iptables
    • in fact, my debian 13 VPS already has this set up by default
    • Docker/UFW Loopback Routing: Bind Docker ports to the bridge gateway IP (e.g., 172.17.0.1) instead of 127.0.0.1 to ensure the host Nginx can route traffic through UFW’s forward chain to containers on custom networks
  • Debug Cloudflare + Nginx + browser first-request failures:
    • net::ERR_QUIC_PROTOCOL_ERROR.QUIC_NETWORK_IDLE_TIMEOUT on first load was caused by browsers probing HTTP/3 (QUIC over UDP/443) against an origin that does not support it
    • Error only appears on first visit (incognito) because the browser falls back to HTTP/2 after QUIC fails
    • Cloudflare proxy previously masked the issue by terminating HTTP/3 at the edge
    • Fixed by disabling Cloudflare → Speed → Protocol Optimization → HTTP/3 (with QUIC)
    • Confirmed via Nginx logs showing SSL_read() failed / bad record mac from protocol mismatch

Getting started

Clone the repo with frontend submodules

git clone --recurse-submodules [email protected]:minhhoccode111/fiber-clean-realworld.git

Run everything

Simple start all services (Nginx, FE, BE, DB)

make compose-up-all

Development

Start DB-only to develop

make compose-up-db

Then start the app (with Swagger)

make run-swag

Or run with debugger (F5) using .vscode/launch.json config

Or run with air using .air.toml for live reload

air

And for many more make commands please checkout with

make help

Generate types.ts

Endpoints

       # Auth
POST   /users
POST   /users/login
GET    /user
PUT    /user

       # Article, Favorite, Comments
POST   /articles
GET    /articles
                ?tag={tag1,tag2}
                &author={username}
                &favorited={username}
                &limit={limit}
                &offset={offset}
GET    /articles/feed
GET    /articles/{slug}
PUT    /articles/{slug}
DELETE /articles/{slug}
POST   /articles/{slug}/favorite
DELETE /articles/{slug}/favorite
POST   /articles/{slug}/comments
GET    /articles/{slug}/comments
DELETE /articles/{slug}/comments/{commentID}

       # Profiles
GET    /profiles/{username}
POST   /profiles/{username}/follow
DELETE /profiles/{username}/follow

       # Tags
GET    /tags

Database Design

- Users
       - id
idx    - email     - unique
idx    - username  - unique
       - password
       - image
       - bio
       - created_at
       - updated_at
- Articles
       - id
       - author_id
idx    - slug      - unique
       - body
       - title
       - description
       - created_at
       - updated_at
       - deleted_at
- Comments
       - id
       - article_id
       - author_id
       - body
       - created_at
       - deleted_at
- ArticleTags
       - article_id
       - tag_id
- Tags
       - id
idx    - name      - unique
- Favorites
       - user_id
       - article_id
- Follows
       - follower_id
       - following_id
  • User vs. Article: One-to-Many
  • Article vs. Comment: One-to-Many
  • Article vs. Tag: Many-to-Many
  • Follow User vs. User: Many-to-Many
  • Favorite User vs. Article: Many-to-Many

Test

Run the test script

./docs/api-test/run-api-tests.sh

Expected output

┌─────────────────────────┬──────────────────┬─────────────────┐
│                         │         executed │          failed │
├─────────────────────────┼──────────────────┼─────────────────┤
│              iterations │                1 │               0 │
├─────────────────────────┼──────────────────┼─────────────────┤
│                requests │               32 │               0 │
├─────────────────────────┼──────────────────┼─────────────────┤
│            test-scripts │               48 │               0 │
├─────────────────────────┼──────────────────┼─────────────────┤
│      prerequest-scripts │               18 │               0 │
├─────────────────────────┼──────────────────┼─────────────────┤
│              assertions │              335 │               0 │
├─────────────────────────┴──────────────────┴─────────────────┤
│ total run duration: 16.8s                                    │
├──────────────────────────────────────────────────────────────┤
│ total data received: 23kB (approx)                           │
├──────────────────────────────────────────────────────────────┤
│ average response time: 9ms [min: 1ms, max: 62ms, s.d.: 16ms] │
└──────────────────────────────────────────────────────────────┘

Todo

  • Add admin/user roles to manage users' content
  • Add custom errors instead of leaking pgx errors to the outer layer
  • Add support for both jwt-in-header and jwt-in-cookie authentication
  • Fix all make linter-... errors
  • Add updating of an article's tags list
  • Add filtering of articles using multiple tags
  • Add mocks, unit tests
  • Add caching
  • Try GraphQL

Resources

Contributing

Contributions are welcome and highly appreciated!
This project follows the RealWorld API spec — please make sure your changes remain compliant.

About

Realworld Implementation (Medium BE Clone) Using Fiber & Clean Architecture

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors