https://github.com/learning-cloud-native-go/myapp
π How to build a Dockerized RESTful API application using Go.
https://github.com/learning-cloud-native-go/myapp
cloud-native docker go golang kubernetes microservice restful-api tutorial
Last synced: 14 days ago
JSON representation
π How to build a Dockerized RESTful API application using Go.
- Host: GitHub
- URL: https://github.com/learning-cloud-native-go/myapp
- Owner: learning-cloud-native-go
- License: mit
- Created: 2019-08-02T11:54:58.000Z (over 6 years ago)
- Default Branch: main
- Last Pushed: 2024-05-12T11:42:55.000Z (over 1 year ago)
- Last Synced: 2024-08-03T23:29:58.167Z (over 1 year ago)
- Topics: cloud-native, docker, go, golang, kubernetes, microservice, restful-api, tutorial
- Language: Go
- Homepage: https://learning-cloud-native-go.github.io
- Size: 66.4 MB
- Stars: 816
- Watchers: 19
- Forks: 65
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
- awesome-golang-repositories - myapp
README
[](https://www.buymeacoffee.com/dumindu)
# Learning Cloud Native Go - myapp
## π Batteries Included
- Use of Go linters, Docker, Docker Compose, Alpine development images, and Distroless production images.
- Use of [GORM CLI](https://gorm.io/cli/) to generate Go generics-based database repositories.
- Use of [Goose](https://github.com/pressly/goose) to build a DB migration CLI with embedded migrations.
- Use of [Validator.v10](https://github.com/go-playground/validator) to validate forms via Go generics-based, fail-fast validation middleware.
- Use of [Zerolog](https://github.com/rs/zerolog) to generate request logs and centralized Syslog logging.
- Use of [Swag.v2](https://github.com/swaggo/swag) to generate OpenAPI v3 specifications.
- Use of GitHub Actions to run linters and tests, and to build and push production images to the registry.
> π‘ Go v1.26rc2 for json/v2 and new compiler features.
| Environment | Go 1.26rc2 Image Size | Postgres v18 Image Size |
|----------------|-----------------------|-------------------------|
| Development | 777 MB | 300MB |
| Production | 30 MB | |
## π Available Commands
```just
$ just
πMYAPP
help # List available commands
go-run cmd="app" # Run a specific cmd (defaults to app)
go-run-migrate cmd="up" # Run database migrations (defaults to up)
build # Run docker compose build
up cmd="" # Run docker compose up
down # Run docker compose down
lint # Run lints using gofumpt, go vet, staticcheck and govulncheck
test # Run tests
gen # Run go generate for all packages
gen-openapi # Generate openapi v3 specification using swag v2
gen-gorm-repos # Generate gorm repositories using gorm cli
```
## π¬ Endpoints
| Name | HTTP Method | Route |
|-------------|-------------|----------------|
| Health | GET | /livez |
| List Books | GET | /v1/books |
| Create Book | POST | /v1/books |
| Read Book | GET | /v1/books/{id} |
| Update Book | PUT | /v1/books/{id} |
| Delete Book | DELETE | /v1/books/{id} |
## ποΈ Database Design
| Column Name | Datatype | Not Null | Primary Key |
|----------------|-----------|----------|-------------|
| id | UUID | β
| β
|
| title | TEXT | β
| |
| author | TEXT | β
| |
| published_date | DATE | β
| |
| image_url | TEXT | | |
| description | TEXT | | |
| created_at | TIMESTAMP | β
| |
| updated_at | TIMESTAMP | β
| |
## βοΈ Form Validation
```json
{
"errors": {
"title": "This is a required field",
"author": "This can only contain alphabetic and space characters",
"published_date": "This must be a valid date",
"image_url": "This must be a valid URL"
}
}
```
## π Request Logs and Centralized Syslog Logging
```json lines
db-1 | 2018-01-10 01:00:00.000 +08 [1] LOG: database system is ready to accept connections
Container myapp-db-1 Healthy
app-1 | 2018/01/10 01:00:00 OK 00001_create_books_table.sql (2.21ms)
app-1 | 2018/01/10 01:00:00 goose: successfully migrated database to version: 1
app-1 |
app-1 | {"level":"info","time":"2018-01-10T02:00:00+08:00","message":"Starting server :8080"}
app-1 |
app-1 | [7.218ms] [rows:1] INSERT INTO books (id, created_at, updated_at, title, author, published_date, image_url, description) VALUES ('38ba23d1-9565-40ed-b781-aacd2f84018d', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'Death Note', 'Light Yagami', '2006-10-04 00:00:00', 'https://static.wikia.nocookie.net/deathnote/images/9/94/A_Death_Note.jpg', 'A supernatural volume dropped into the human world by the Shinigami Ryuk') RETURNING *
app-1 | {"level":"info","request_id":"d5mq7oi6hkls7397s43g","received_time":"2018-01-10T03:00:00+08:00","method":"POST","url":"/v1/books","header_size":135,"body_size":0,"agent":"yaak","referer":"","proto":"HTTP/1.1","remote_ip":"192.168.65.1","server_ip":"172.19.0.3","status":201,"resp_header_size":47,"resp_body_size":296,"latency":8.307,"time":"2018-01-10T03:00:00+08:00"}
app-1 | {"level":"info","request_id":"d5mq7oi6hkls7397s43g","id":"38ba23d1-9565-40ed-b781-aacd2f84018d","time":"2018-01-10T03:00:00+08:00","message":"new book created"}
app-1 |
app-1 | [2.541ms] [rows:1] SELECT * FROM books WHERE id = '38ba23d1-9565-40ed-b781-aacd2f84018d'
app-1 | {"level":"info","request_id":"d5mqa6a6hkls7397s44g","received_time":"2018-01-10T04:00:00+08:00","method":"GET","url":"/v1/books/38ba23d1-9565-40ed-b781-aacd2f84018d","header_size":82,"body_size":0,"agent":"yaak","referer":"","proto":"HTTP/1.1","remote_ip":"192.168.65.1","server_ip":"172.19.0.3","status":200,"resp_header_size":47,"resp_body_size":296,"latency":2.674625,"time":"2018-01-10T04:00:00+08:00"}
app-1 |
app-1 | [3.744ms] [rows:1] UPDATE books SET updated_at=CURRENT_TIMESTAMP, title='Death Note', author='Misa Amane', published_date='2004-11-04 00:00:00', image_url='https://static.wikia.nocookie.net/deathnote/images/9/94/A_Death_Note.jpg', description='Light Yagami''s buried notebook' WHERE id = '38ba23d1-9565-40ed-b781-aacd2f84018d' RETURNING *
app-1 | {"level":"info","request_id":"d5mqesa6hkls7397s45g","id":"38ba23d1-9565-40ed-b781-aacd2f84018d","time":"2018-01-10T05:00:00+08:00","message":"book updated"}
app-1 | {"level":"info","request_id":"d5mqesa6hkls7397s45g","received_time":"2018-01-10T05:00:00+08:00","method":"PUT","url":"/v1/books/38ba23d1-9565-40ed-b781-aacd2f84018d","header_size":135,"body_size":0,"agent":"yaak","referer":"","proto":"HTTP/1.1","remote_ip":"192.168.65.1","server_ip":"172.19.0.3","status":200,"resp_header_size":47,"resp_body_size":252,"latency":4.018875,"time":"2018-01-10T05:00:00+08:00"}
app-1 |
app-1 | [3.035ms] [rows:1] DELETE FROM books WHERE id = '38ba23d1-9565-40ed-b781-aacd2f84018d' RETURNING true
app-1 | {"level":"info","request_id":"d5mqfgi6hkls7397s460","received_time":"2018-01-10T06:00:00+08:00","method":"DELETE","url":"/v1/books/38ba23d1-9565-40ed-b781-aacd2f84018d","header_size":82,"body_size":0,"agent":"yaak","referer":"","proto":"HTTP/1.1","remote_ip":"192.168.65.1","server_ip":"172.19.0.3","status":200,"resp_header_size":47,"resp_body_size":0,"latency":3.265,"time":"2018-01-10T06:00:00+08:00"}
app-1 | {"level":"info","request_id":"d5mqfgi6hkls7397s460","id":"38ba23d1-9565-40ed-b781-aacd2f84018d","time":"2018-01-10T06:00:00+08:00","message":"book deleted"}
app-1 |
app-1 | [2.573ms] [rows:1] SELECT * FROM books LIMIT 10 OFFSET 0
app-1 | {"level":"info","request_id":"d5mq9gi6hkls7397s440","received_time":"2018-01-10T07:00:00+08:00","method":"GET","url":"/v1/books","header_size":82,"body_size":0,"agent":"yaak","referer":"","proto":"HTTP/1.1","remote_ip":"192.168.65.1","server_ip":"172.19.0.3","status":200,"resp_header_size":47,"resp_body_size":298,"latency":2.926916,"time":"2018-01-10T07:00:00+08:00"}
app-1 |
app-1 |
app-1 | [1.661ms] [rows:0] DELETE FROM books WHERE id = '38ba23d1-9565-40ed-b781-aacd2f84018d' RETURNING true
app-1 | {"level":"info","request_id":"d5mrj8ppsdvs73dkfct0","received_time":"2018-02-01T01:00:00+08:00","method":"DELETE","url":"/v1/books/38ba23d1-9565-40ed-b781-aacd2f84018d","header_size":82,"body_size":0,"agent":"yaak","referer":"","proto":"HTTP/1.1","remote_ip":"192.168.65.1","server_ip":"172.19.0.3","status":404,"resp_header_size":47,"resp_body_size":0,"latency":1.80125,"time":"2018-02-01T01:00:00+08:00"}
app-1 | [1.384ms] [rows:0] UPDATE books SET updated_at=CURRENT_TIMESTAMP, title='Death Note', author='Misa Amane', published_date='2004-11-04 00:00:00', image_url='https://static.wikia.nocookie.net/deathnote/images/9/94/A_Death_Note.jpg', description='Light Yagami''s buried notebook' WHERE id = '38ba23d1-9565-40ed-b781-aacd2f84018d' RETURNING *
app-1 | {"level":"info","request_id":"d5mqjmhqvtmc73foh3dg","received_time":"2018-02-02T08:00:00:00","method":"PUT","url":"/v1/books/38ba23d1-9565-40ed-b781-aacd2f84018d","header_size":135,"body_size":0,"agent":"yaak","referer":"","proto":"HTTP/1.1","remote_ip":"192.168.65.1","server_ip":"172.19.0.3","status":404,"resp_header_size":47,"resp_body_size":0,"latency":1.576,"time":"2018-02-02T08:00:00+08:00"}
// π― Real logs collected locally but with few rearrangements to make it easier to read.
```
## ποΈ Project Folder Structure
```shell
βββ compose.yml
βββ Dockerfile
β
βββ openapi-v3.yml
β
βββ app
β βββ book
β β βββ bookrepo # π‘generated with gorm-cli via the interface in book/repository.go
β β β βββ repository.go
β β βββ form_util.go
β β βββ handler.go
β β βββ repository.go
β βββ router
β βββ router.go
βββ form # π‘Form validation middleware rely on this and pkg folder only
β βββ book.go
βββ model
β βββ book.go
β
βββ config
β βββ config.go
β
βββ cmd # π‘Entrypoint for app and migrate executables
β βββ app
β β βββ main.go
β βββ migrate
β βββ main.go
β βββ migrations
β βββ 00001_create_books_table.sql
β
βββ pkg (middleware, logger, validator, ctxutil, paramsutil, errors)
```
ArgoCD and `kustomization` based cloud native IaC & GitOps setup.
> π‘ Consider moving to a Hub-and-Spoke architecture for Argo CD, combined with a separate repository strategy.
```shell
βββ k8s
β
βββ bootstrap
β βββ argocd
β βββ argocd-config
β βββ clusters
β βββ projects
β βββ applications
β
βββ platform
β βββ metrics-server
β βββ gateway-api
β βββ istio-ambient
β βββ cloudnative-pg
β
βββ components
β βββ myapp-db
βββ services
βββ base
β βββ myapp
βββ overlays
βββ dev
βββ prod
βββ stage
```