Golang codebase containing real world examples (CRUD, auth, advanced patterns, etc) that adheres to the RealWorld spec and API.
- Go + Fiber
- Clean Architecture
- Dependency Injection
- Embedded struct like
PaginationandTimestampsin other struct - Generate mocks with
mockgenandgo 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 squirrelfor query builderlvh.medomainSecure: truecookie flag works withlocalhostbut not something likehttp://fe.lvh.mebecause we needhttpsat that point- Go debugger with
.vscode/launch.json - In
repolayer, we don't returnpgx.ErrNoRowsto outer layer to handle because it will make the router layer depend on postgresql implementation, instead we return our custom not found errorentity.ErrNoRows - Generate TypeScript API types
- A response struct with pointer fields, shouldn't use
omitemptybecause handlingprofile?: ProfilePreview | null;is harder thanprofile: ProfilePreview | null;in TypeScript - Postgres 18 needs
db_data:/var/lib/postgresqland notdb_data:/var/lib/postgresql/data - To allow
nginxservice to read mounted host files, we need to runchmod -R 755 ./frontend/dist jwt-in-cookieis more secure thanjwt-in-headerbut 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 of127.0.0.1to 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_TIMEOUTon 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 macfrom protocol mismatch
Clone the repo with frontend submodules
git clone --recurse-submodules [email protected]:minhhoccode111/fiber-clean-realworld.gitSimple start all services (Nginx, FE, BE, DB)
make compose-up-allStart DB-only to develop
make compose-up-dbThen start the app (with Swagger)
make run-swagOr 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 helpGenerate types.ts
# 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- 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
Run the test script
./docs/api-test/run-api-tests.shExpected 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] │
└──────────────────────────────────────────────────────────────┘- 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
- My first Realworld implementation using Golang: realworldgo
- Starter Template using Golang Clean Architecture
Contributions are welcome and highly appreciated!
This project follows the RealWorld API
spec — please make sure your changes
remain compliant.