feat: first

This commit is contained in:
2025-08-13 19:12:51 +02:00
commit fd2ba2e999
9 changed files with 358 additions and 0 deletions

43
.gitignore vendored Normal file
View File

@@ -0,0 +1,43 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
# Build output
bin/
# Generated protobuf files
proto/**/*.pb.go
proto/**/*.pb.gw.go
# Buf lock file
buf.lock
# IDE files
.vscode/
.idea/
*.swp
*.swo
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

68
README.md Normal file
View File

@@ -0,0 +1,68 @@
# Go gRPC Gateway Template
A simplified Go project template for gRPC services with HTTP/JSON gateway support, based on the [Go Standard Project Layout](https://github.com/golang-standards/project-layout).
## Project Structure
```
├── cmd/server/ # Main application entry point
├── internal/
│ ├── handler/ # gRPC and gateway handlers
│ └── service/ # Business logic
├── pkg/pb/ # Generated protobuf files (auto-generated)
├── api/v1/ # Protocol buffer definitions
└── justfile # Task runner configuration
```
## Prerequisites
- Go 1.21+
- Protocol Buffers compiler (`protoc`)
- [just](https://github.com/casey/just) task runner
- [nushell](https://www.nushell.sh/) (for justfile execution)
## Getting Started
1. Install dependencies:
```bash
just deps
```
2. Generate protobuf files:
```bash
just proto
```
3. Build and run the server:
```bash
just dev
```
## Available Commands
- `just proto` - Generate protobuf files
- `just build` - Build the server binary
- `just run` - Run the server
- `just dev` - Full development workflow (deps + proto + run)
- `just test` - Run tests
- `just lint` - Run linter
- `just clean` - Clean build artifacts
## API Endpoints
The server runs on two ports:
- gRPC server: `:8080`
- HTTP gateway: `:8081`
### Example HTTP endpoints:
- `GET /v1/examples` - List all examples
- `POST /v1/examples` - Create a new example
- `GET /v1/examples/{id}` - Get a specific example
## Monorepo Integration
This template is designed to be integration-friendly for monorepo structures by:
- Excluding shared proto folders
- Using internal packages for service-specific logic
- Minimal external dependencies
- Clear separation of concerns

17
buf.gen.yaml Normal file
View File

@@ -0,0 +1,17 @@
version: v2
plugins:
# generate go structs for protocol buffer definition
- local: protoc-gen-go
out: .
opt:
- paths=source_relative
# generate gRPC stubs in golang
- local: protoc-gen-go-grpc
out: .
opt:
- paths=source_relative
# generate reverse proxy from protocol definitions
- local: protoc-gen-grpc-gateway
out: .
opt:
- paths=source_relative

10
buf.yaml Normal file
View File

@@ -0,0 +1,10 @@
version: v2
name: buf.build/grpc-gateway-template/proto
deps:
- buf.build/googleapis/googleapis
breaking:
use:
- FILE
lint:
use:
- DEFAULT

84
cmd/server/main.go Normal file
View File

@@ -0,0 +1,84 @@
package main
import (
"context"
"log"
"net"
"net/http"
"sync"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
helloworldpb "go-grpc-gateway-template/proto/helloworld"
)
type server struct {
helloworldpb.UnimplementedGreeterServer
}
func (s *server) SayHello(ctx context.Context, req *helloworldpb.HelloRequest) (*helloworldpb.HelloReply, error) {
return &helloworldpb.HelloReply{
Message: "Hello " + req.GetName(),
}, nil
}
func main() {
// Create a listener on TCP port
lis, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatalln("Failed to listen:", err)
}
// Create a gRPC server object
s := grpc.NewServer()
// Attach the Greeter service to the server
helloworldpb.RegisterGreeterServer(s, &server{})
var wg sync.WaitGroup
wg.Add(2)
// Serve gRPC server
log.Println("Serving gRPC on 0.0.0.0:8080")
go func() {
defer wg.Done()
if err := s.Serve(lis); err != nil {
log.Fatalln("Failed to serve:", err)
}
}()
// Create a client connection to the gRPC server we just started
// This is where the gRPC-Gateway proxies the requests
conn, err := grpc.DialContext(
context.Background(),
"0.0.0.0:8080",
grpc.WithBlock(),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
log.Fatalln("Failed to dial server:", err)
}
gwmux := runtime.NewServeMux()
// Register Greeter
err = helloworldpb.RegisterGreeterHandler(context.Background(), gwmux, conn)
if err != nil {
log.Fatalln("Failed to register gateway:", err)
}
gwServer := &http.Server{
Addr: ":8090",
Handler: gwmux,
}
log.Println("Serving gRPC-Gateway on http://0.0.0.0:8090")
go func() {
defer wg.Done()
if err := gwServer.ListenAndServe(); err != nil {
log.Fatalln("Failed to serve gRPC-Gateway:", err)
}
}()
wg.Wait()
}

19
go.mod Normal file
View File

@@ -0,0 +1,19 @@
module go-grpc-gateway-template
go 1.23.0
toolchain go1.23.1
require (
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822
google.golang.org/grpc v1.74.2
google.golang.org/protobuf v1.36.6
)
require (
golang.org/x/net v0.40.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.26.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
)

38
go.sum Normal file
View File

@@ -0,0 +1,38 @@
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=

55
justfile Normal file
View File

@@ -0,0 +1,55 @@
#!/usr/bin/env nu
# Update buf dependencies
buf-deps:
buf dep update
# Generate protobuf files
proto: buf-deps
buf generate
# Build the server binary
build: proto
go build -o bin/server cmd/server/main.go
# Run the server
run: build
./bin/server
# Clean build artifacts
clean:
rm -rf bin/
rm -rf pkg/pb/
# Run tests
test:
go test ./...
# Run linter
lint:
golangci-lint run
# Install required tools and dependencies
install-deps:
@echo "Installing buf..."
@if ! command -v buf >/dev/null 2>&1; then \
mkdir -p ~/.local/bin && \
curl -sSL "https://github.com/bufbuild/buf/releases/latest/download/buf-$(uname -s)-$(uname -m)" -o "$HOME/.local/bin/buf" && \
chmod +x "$HOME/.local/bin/buf"; \
echo "buf installed to ~/.local/bin/buf"; \
echo "Make sure ~/.local/bin is in your PATH"; \
else \
echo "buf already installed"; \
fi
@echo "Installing protoc plugins..."
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/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest
# Download and tidy dependencies
deps:
go mod download
go mod tidy
# Development workflow
dev: deps proto run

View File

@@ -0,0 +1,24 @@
syntax = "proto3";
option go_package = "go-grpc-gateway-template/proto/helloworld";
package helloworld;
import "google/api/annotations.proto";
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {
option (google.api.http) = {
post: "/v1/example/echo"
body: "*"
};
}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}