Skip to content

Commit ed4896e

Browse files
committed
Let's go
0 parents  commit ed4896e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+2825
-0
lines changed

.env.example

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
PORT=8000
2+
IDLE_TIMEOUT=5
3+
ENABLE_PRINT_ROUTE=true
4+
5+
APP_ENV=local
6+
APP_NAME=example api
7+
APP_VERSION=0.0.1
8+
9+
LOG_LEVEL=debug
10+
11+
DB_HOST=localhost
12+
DB_PORT=4000
13+
DB_USER=root
14+
DB_PASSWORD=
15+
DB_NAME=test
16+
DB_USE_SSL=false
17+
DB_AUTO_MIGRATE=true
18+
19+
REDIS_ADDR=localhost
20+
REDIS_PORT=6379
21+
22+
JWT_SECRET=c62028e29ccec50c690c3579ea5f87b4f5cfa92bd4cf039ae0b05975b8cbc1b4

.gitignore

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Binaries for programs and plugins
2+
*.exe
3+
*.exe~
4+
*.dll
5+
*.so
6+
*.dylib
7+
8+
# Test binary, build outputs
9+
*.test
10+
*.out
11+
12+
# Output of the go coverage tool
13+
*.coverprofile
14+
15+
# Go workspace file
16+
go.work
17+
go.work.sum
18+
19+
# Logs and debug
20+
*.log
21+
22+
# Dependency directories (vendor/)
23+
vendor/
24+
25+
# IDEs and editors
26+
.vscode/
27+
.idea/
28+
*.swp
29+
30+
# OS generated files
31+
.DS_Store
32+
Thumbs.db
33+
34+
# Environment variable files
35+
.env
36+
.env.local
37+
.env.*.local
38+
39+
# Redis dump file (if used locally)
40+
dump.rdb
41+
42+
# Database files (if TiDB or SQLite dumps exist)
43+
*.db
44+
*.sql
45+
46+
# Cache and build directories
47+
bin/
48+
build/
49+
dist/

README.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Golang CRUD Service
2+
3+
This is a sample CRUD service built with Golang, demonstrating a layered architecture that separates concerns into handler, service, repository, and model layers. The project uses **TiDB** as the relational database and **Redis** for caching and other in-memory operations.
4+
5+
## 🚀 Project Purpose
6+
7+
Build a sample CRUD service API with Golang to showcase clean code structure and common best practices in web API development.
8+
9+
## 🛠️ Tech Stack
10+
11+
- **[Golang](https://golang.org)** — Core programming language
12+
- **[Fiber](https://gofiber.io)** — Fast HTTP router
13+
- **[GORM](https://gorm.io)** — ORM for database operations
14+
- **[TiDB](https://www.pingcap.com)** — Distributed SQL database
15+
- **[Redis](https://redis.io)** — In-memory data store
16+
- **[Validator](https://github.com/go-playground/validator)** — Input validation
17+
- **[Copier](https://github.com/jinzhu/copier)** — Object copying for DTOs
18+
19+
## 📁 Project Structure
20+
21+
```
22+
├── cmd/
23+
│ └── app/
24+
│ └── main.go # Application entry point
25+
├── pkg/ # Shared package configure
26+
│ └── bcrypt/
27+
| └── bcrypt.go # Bcrypt hashing
28+
│ └── redis/
29+
| └── redis.go # Redis configuration
30+
│ └── db/
31+
| └── db.go # DB configuration
32+
├── api/
33+
│ └── student/
34+
│ ├── route.go # Student-related endpoints
35+
│ └── handler.go # Logic for handling student API requests
36+
├── internal/
37+
│ ├── service/ # Business logic, called from handler
38+
│ ├── repository/ # Data access layer (calls GORM & queries DB)
39+
│ ├── model/ # Database models/entities
40+
│ └── dto/ # DTOs for transforming request/response data
41+
```
42+
43+
## 📌 Key Concepts
44+
45+
- **Layered Architecture**: Divides the project into clear layers for maintainability and scalability.
46+
- **DTO Pattern**: Uses `copier` to map between internal models and response/request structures.
47+
- **Validation**: Ensures request payloads are validated with `go-playground/validator`.
48+
- **ORM**: Leverages GORM to interact with TiDB in a concise and type-safe way.
49+
50+
## 🧪 Running the Project
51+
52+
1. **Configure Environment**
53+
Create a `.env` file. find in a `.env.example` file for environment:
54+
- JWT Secret key
55+
- TiDB connection
56+
- Redis connection
57+
58+
2. **Run the App**
59+
```bash
60+
go run cmd/app/main.go
61+
```
62+
63+
## 🔧 Future Enhancements
64+
65+
- Add unit tests and integration tests
66+
- Implement authentication and authorization
67+
- Dockerize the application
68+
- Add Swagger/OpenAPI documentation
69+
70+
## 📄 License
71+
72+
This project is open-source and available under the [MIT License](LICENSE).

api/auth/handler.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package auth
2+
3+
import (
4+
"learn-fiber/api/response"
5+
"learn-fiber/config"
6+
"learn-fiber/internal/util/common"
7+
"time"
8+
9+
"github.com/gofiber/fiber/v2"
10+
"github.com/golang-jwt/jwt/v5"
11+
"gorm.io/gorm"
12+
)
13+
14+
type Handler struct {
15+
db *gorm.DB
16+
}
17+
18+
func NewHandler(db *gorm.DB) *Handler {
19+
return &Handler{db: db}
20+
}
21+
22+
func (h *Handler) Login(c *fiber.Ctx) error {
23+
req, err := common.GetRequestBody[Request](c)
24+
if err != nil {
25+
return err
26+
}
27+
28+
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
29+
"username": req.Username,
30+
"role": "admin",
31+
"exp": time.Now().Add(time.Hour * 1).Unix(),
32+
})
33+
34+
secret := []byte(config.Cfg.JWT.Secret)
35+
res, err := token.SignedString(secret)
36+
if err != nil {
37+
return c.Status(500).JSON(fiber.Map{"error": "Could not generate token"})
38+
}
39+
return response.SendSuccessResponse(c, res)
40+
}

api/auth/request.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package auth
2+
3+
type Request struct {
4+
Username string `json:"username" validate:"required"`
5+
Password string `json:"password" validate:"required"`
6+
}

api/auth/routes.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package auth
2+
3+
import (
4+
"github.com/gofiber/fiber/v2"
5+
"gorm.io/gorm"
6+
)
7+
8+
func AddRoutes(r fiber.Router, db *gorm.DB) {
9+
handler := NewHandler(db)
10+
router := r.Group("/auth")
11+
router.Post("login", handler.Login)
12+
}

api/department/handler.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package department
2+
3+
import (
4+
"learn-fiber/api/response"
5+
"learn-fiber/internal/dto"
6+
"learn-fiber/internal/ierror"
7+
"learn-fiber/internal/service"
8+
"learn-fiber/internal/util/common"
9+
"learn-fiber/internal/util/validator"
10+
11+
"github.com/gofiber/fiber/v2"
12+
"github.com/jinzhu/copier"
13+
"gorm.io/gorm"
14+
)
15+
16+
type Handler struct {
17+
service service.Department
18+
}
19+
20+
func NewHandler(db *gorm.DB) *Handler {
21+
svc := service.NewDepartment(db)
22+
return &Handler{service: svc}
23+
}
24+
25+
func (h *Handler) GetList(c *fiber.Ctx) error {
26+
query, _ := common.GetQueryParam[dto.QueryParams](c)
27+
if hasError, err := validator.V.Valid(query); hasError {
28+
return ierror.NewValidationError(err)
29+
}
30+
result, page, err := h.service.GetAllDepartment(query)
31+
if err != nil {
32+
return response.SendFailResponse(c, err.Error())
33+
}
34+
var res []Response
35+
_ = copier.Copy(&res, &result)
36+
return response.SendSuccessResponsePaging(c, res, page)
37+
}
38+
39+
func (h *Handler) GetById(c *fiber.Ctx) error {
40+
id := c.Params("id")
41+
result, err := h.service.GetDepartment(id)
42+
if err != nil {
43+
return response.SendFailResponse(c, err.Error())
44+
}
45+
var res Response
46+
_ = copier.Copy(&res, &result)
47+
return response.SendSuccessResponse(c, res)
48+
}
49+
50+
func (h *Handler) Create(c *fiber.Ctx) error {
51+
req, _ := common.GetRequestBody[dto.DepartmentCreateRequest](c)
52+
if hasError, err := validator.V.Valid(req); hasError {
53+
return ierror.NewValidationError(err)
54+
}
55+
result, err := h.service.CreateDepartment(req)
56+
if err != nil {
57+
return response.SendFailResponse(c, err.Error())
58+
}
59+
var res Response
60+
_ = copier.Copy(&res, &result)
61+
return response.SendSuccessResponse(c, res)
62+
}
63+
64+
func (h *Handler) Update(c *fiber.Ctx) error {
65+
req, _ := common.GetRequestBody[dto.DepartmentUpdateRequest](c)
66+
if hasError, err := validator.V.Valid(req); hasError {
67+
return ierror.NewValidationError(err)
68+
}
69+
result, err := h.service.UpdateDepartment(req)
70+
if err != nil {
71+
return response.SendFailResponse(c, err.Error())
72+
}
73+
var res Response
74+
_ = copier.Copy(&res, &result)
75+
return response.SendSuccessResponse(c, res)
76+
}
77+
78+
func (h *Handler) Delete(c *fiber.Ctx) error {
79+
id := c.Params("id")
80+
_ = h.service.DeleteDepartment(id)
81+
return response.SendSuccessResponse(c, nil)
82+
}

api/department/response.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package department
2+
3+
type Response struct {
4+
Id string `json:"id"`
5+
Code string `json:"code"`
6+
DepartmentEN string `json:"departmentEN"`
7+
DepartmentKM string `json:"departmentKM"`
8+
}

api/department/routes.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package department
2+
3+
import (
4+
"learn-fiber/internal/middleware"
5+
6+
"github.com/gofiber/fiber/v2"
7+
"gorm.io/gorm"
8+
)
9+
10+
func AddRoutes(r fiber.Router, db *gorm.DB) {
11+
handler := NewHandler(db)
12+
router := r.Group("/department",
13+
middleware.JWTProtected(),
14+
middleware.RequireRole("admin"),
15+
)
16+
router.Put("", handler.Update)
17+
router.Get("", handler.GetList)
18+
router.Post("", handler.Create)
19+
router.Get(":id", handler.GetById)
20+
router.Delete(":id", handler.Delete)
21+
}

api/response/error.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package response
2+
3+
type ErrorResponse struct {
4+
Error ErrorObject `json:"error"`
5+
}
6+
7+
type ErrorObject struct {
8+
Code string `json:"code"`
9+
Message string `json:"message"`
10+
ValidationErrors []ValidationError `json:"validationErrors,omitempty"`
11+
TraceID string `json:"traceID"`
12+
}
13+
14+
type ValidationError struct {
15+
Field string `json:"field"`
16+
Message string `json:"message"`
17+
}

0 commit comments

Comments
 (0)