Build a User CRUD Service from Scratch with Go‑Kratos
This step‑by‑step guide shows how to use the Go‑Kratos microservice framework to create a clean‑architecture user service, covering environment setup, project scaffolding, Protobuf API definition, code generation, layered implementation (biz, data, service), server registration, and testing with curl.
What is Kratos and why choose it?
Kratos is an open‑source Go microservice development framework created by Bilibili. It aims to let developers focus on business logic without being burdened by infrastructure.
Four Core Advantages
Microservices : independent deployment and evolution via modular layout and service registration/discovery.
Specified API : unified API definition using Protobuf with automatic HTTP/gRPC generation.
Plug‑able : optional tracing, metrics, logging, rate‑limit components.
Environment Preparation (including domestic acceleration)
go version
# Recommended: 1.24+ # Install Kratos CLI (foreign source may be slow)
go install github.com/go-kratos/kratos/cmd/kratos/v2@latest
# ✅ Domestic recommendation: Gitee source (faster)
go install -v github.com/go-kratos/kratos/cmd/kratos/v2@latest
# If pulling fails, set GOPROXY
export GOPROXY=https://goproxy.cn,direct # Install Protobuf compiler & plugins
brew install protobuf # macOS
sudo apt install -y protobuf-compiler # Ubuntu
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
go install github.com/go-kratos/kratos/cmd/protoc-gen-go-http/v2@latest
go install github.com/go-kratos/kratos/cmd/protoc-gen-go-errors/v2@latest kratos -v
# Example output: kratos version v2.7.2Create Project Skeleton
# Use Gitee source (domestic friendly)
kratos new user-service -r https://gitee.com/go-kratos/kratos-layout.git
cd user-service
go mod tidyGenerated directory (key parts):
user-service/
├── api/ # ← Protobuf API definitions
├── cmd/
│ └── user-service/ # ← entry point
├── internal/
│ ├── biz/ # ← business logic (UseCase)
│ ├── data/ # ← data access (Repo/DB)
│ ├── service/ # ← service implementation (API binding)
│ └── server/ # ← HTTP/gRPC server registration
├── go.mod
└── Makefile💡 Key point: google.api.http automatically generates RESTful routes.
Define User API (Protobuf)
syntax = "proto3";
package api.user.v1;
option go_package = "user-service/api/user/v1;v1";
import "google/api/annotations.proto";
import "errors/errors.proto"; // ← custom errors
message User {
int64 id = 1;
string name = 2;
string email = 3;
int32 age = 4;
}
message CreateUserRequest { string name = 1; string email = 2; int32 age = 3; }
message CreateUserReply { User user = 1; }
message GetUserRequest { int64 id = 1; }
message GetUserReply { User user = 1; }
message UpdateUserRequest { int64 id = 1; string name = 2; string email = 3; int32 age = 4; }
message UpdateUserReply { User user = 1; }
message DeleteUserRequest { int64 id = 1; }
message DeleteUserReply { bool ok = 1; }
message ListUserRequest { int32 page = 1; int32 size = 2; }
message ListUserReply { repeated User users = 1; }
extend google.protobuf.MethodOptions {
kratos.api.Errors errors = 1111; // type‑safe error constructor
}
service UserService {
rpc CreateUser(CreateUserRequest) returns (CreateUserReply) {
option (google.api.http) = { post: "/v1/users" body: "*" };
}
rpc GetUser(GetUserRequest) returns (GetUserReply) {
option (google.api.http) = { get: "/v1/users/{id}" };
option (errors) = { rpc_error: [{ code: 404 reason: "USER_NOT_FOUND" message: "user not found" }] };
}
rpc UpdateUser(UpdateUserRequest) returns (UpdateUserReply) {
option (google.api.http) = { put: "/v1/users/{id}" body: "*" };
}
rpc DeleteUser(DeleteUserRequest) returns (DeleteUserReply) {
option (google.api.http) = { delete: "/v1/users/{id}" };
}
rpc ListUser(ListUserRequest) returns (ListUserReply) {
option (google.api.http) = { get: "/v1/users" };
}
}🔍 Key point: errors/errors.proto + option (errors) generates type‑safe error constructors, e.g., v1.ErrorUserNotFound().Err() .
Generate Code (Protobuf → Go)
# Or run manually
kratos proto client api/user/v1/user.protoGenerated files:
api/user/v1/
├── user.pb.go # Protobuf serialization
├── user_grpc.pb.go # gRPC stub
└── user_http.pb.go # HTTP route binding (with parameter parsing)✅ At this point HTTP/gRPC dual‑protocol interfaces are generated automatically – no manual routing needed!
Layered Implementation: CRUD Logic
1. Business Model & Interface (internal/biz/user.go)
package biz
import "context"
type User struct {
ID int64
Name string
Email string
Age int32
}
// Repository interface
type UserRepo interface {
Save(context.Context, *User) (*User, error)
Get(context.Context, int64) (*User, error)
Update(context.Context, *User) (*User, error)
Delete(context.Context, int64) error
List(context.Context, int32, int32) ([]*User, error)
}
// UseCase
type UserUsecase struct{ repo UserRepo }
func NewUserUsecase(repo UserRepo) *UserUsecase { return &UserUsecase{repo: repo} }
func (uc *UserUsecase) Create(ctx context.Context, u *User) (*User, error) { return uc.repo.Save(ctx, u) }
func (uc *UserUsecase) Get(ctx context.Context, id int64) (*User, error) { return uc.repo.Get(ctx, id) }
// Update, Delete, List omitted for brevity2. Data Layer (in‑memory) – internal/data/user.go
package data
import (
"context"
"sync/atomic"
"user-service/internal/biz"
)
type userRepo struct{ data map[int64]*biz.User; seq int64 }
func NewUserRepo() biz.UserRepo { return &userRepo{data: make(map[int64]*biz.User)} }
func (r *userRepo) Save(_ context.Context, u *biz.User) (*biz.User, error) {
id := atomic.AddInt64(&r.seq, 1)
u.ID = id
r.data[id] = u
return u, nil
}
func (r *userRepo) Get(_ context.Context, id int64) (*biz.User, error) {
u, ok := r.data[id]
if !ok { return nil, nil }
return u, nil
}
func (r *userRepo) Update(_ context.Context, u *biz.User) (*biz.User, error) {
if _, ok := r.data[u.ID]; !ok { return nil, nil }
r.data[u.ID] = u
return u, nil
}
func (r *userRepo) Delete(_ context.Context, id int64) error { delete(r.data, id); return nil }
func (r *userRepo) List(_ context.Context, page, size int32) ([]*biz.User, error) {
var users []*biz.User
for _, u := range r.data { users = append(users, u) }
return users, nil
}3. Service Layer – internal/service/user.go
package service
import (
"context"
v1 "user-service/api/user/v1"
"user-service/internal/biz"
)
type UserService struct{ v1.UnimplementedUserServiceServer; uc *biz.UserUsecase }
func NewUserService(uc *biz.UserUsecase) *UserService { return &UserService{uc: uc} }
func (s *UserService) CreateUser(ctx context.Context, req *v1.CreateUserRequest) (*v1.CreateUserReply, error) {
u := &biz.User{Name: req.Name, Email: req.Email, Age: req.Age}
created, err := s.uc.Create(ctx, u)
if err != nil { return nil, err }
return &v1.CreateUserReply{User: convertUser(created)}, nil
}
func (s *UserService) GetUser(ctx context.Context, req *v1.GetUserRequest) (*v1.GetUserReply, error) {
u, err := s.uc.Get(ctx, req.Id)
if err != nil { return nil, err }
if u == nil { return nil, v1.ErrorUserNotFound("user not found").Err() }
return &v1.GetUserReply{User: convertUser(u)}, nil
}
// UpdateUser, DeleteUser, ListUser omitted for brevity
func convertUser(u *biz.User) *v1.User {
return &v1.User{Id: u.ID, Name: u.Name, Email: u.Email, Age: u.Age}
}Register Service & Start
1. Modify internal/server/http.go
// In NewHTTPServer add:
v1.RegisterUserServiceHTTPServer(srv, service.NewUserService(uc))2. Modify internal/server/grpc.go
// In NewGRPCServer add:
v1.RegisterUserServiceServer(srv, service.NewUserService(uc))3. Initialise dependencies in main()
repo := data.NewUserRepo()
uc := biz.NewUserUsecase(repo)
svc := service.NewUserService(uc)
app := kratos.New(
kratos.Name("user-service"),
kratos.Server(httpSrv, grpcSrv),
)✅ Recommended: use Wire for dependency injection (wire.go generated).
Run & Test
# Start service
go run ./cmd/user-service
# Default ports:
# HTTP: :8000
# gRPC: :9000Test with curl (HTTP)
# Create
curl -X POST http://localhost:8000/v1/users \
-H "Content-Type: application/json" \
-d '{"name":"Tom","email":"[email protected]","age":30}'
# → {"user":{"id":1,"name":"Tom","email":"[email protected]","age":30}}
# Get
curl http://localhost:8000/v1/users/1
# Update
curl -X PUT http://localhost:8000/v1/users/1 \
-d '{"name":"Tom Lee","age":31}'
# List
curl "http://localhost:8000/v1/users?page=1&size=10"
# Delete
curl -X DELETE http://localhost:8000/v1/users/1Conclusion
The article walks through the complete lifecycle of building a Go‑Kratos user service: from environment setup, project scaffolding, Protobuf‑first API design, automatic code generation, clean‑architecture layered implementation, server registration, to practical testing. It also highlights the use of type‑safe error handling and suggests Wire for dependency injection.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Golang Shines
We share daily the latest Golang technical articles, practical resources, language news, tutorials, and real-world projects to help everyone learn and improve.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
