- buf cli - compiles proto files
- docker - containerizes application
- grpc-go - golang gRPC framework
- grpc-gateway - gRPC to JSON reverse proxy generator
- go-gorm - golang SQL ORM library
- postgres - SQL backend
- golang-migrate - golang library to handle SQL migrations
- jwt-go - golang library to interact with json web tokens
- gqlgen - golang library for building GraphQL servers
# install docker
$ brew install docker
# install buf CLI
$ brew tap bufbuild/buf
$ brew install buf
# one time command to download proto dependencies
$ buf mod update
# generate client/swagger/server code from proto files
$ buf generate
# install the tools listed in tools/tools.go
$ go install \
github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2 \
# ... etc
# generate graphql code from schema
$ gqlgen generate
docker-compose up -d
Now you can test out curling your gRPC server via grpcurl
$ grpcurl -plaintext localhost:8080 list
grpc.health.v1.Health
grpc.reflection.v1alpha.ServerReflection
template.AuthService
template.HealthService
template.TodoService
$ grpcurl -plaintext localhost:8080 describe template.AuthService
template.AuthService is a service:
service AuthService {
rpc Me ( .google.protobuf.Empty ) returns ( .template.User ) {
option (.google.api.http) = { get:"/auth/me" };
}
rpc Signup ( .template.SignupRequest ) returns ( .template.TokenResponse ) {
option (.google.api.http) = { post:"/auth/signup" body:"*" };
option (.grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { security:<> };
}
rpc Token ( .template.TokenRequest ) returns ( .template.TokenResponse ) {
option (.google.api.http) = { post:"/auth/token" body:"*" };
option (.grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { security:<> };
}
}
$ grpcurl -plaintext \
-d '{ "email": "[email protected]", "password": "mypass", "username": "pepsmooth", "given_name": "Pep", "family_name": "Smooth", "nickname": "Pep" }' \
localhost:8080 template.AuthService/Signup
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MjI4MzE3MjgsImp0aSI6IjA1NDcyOTU2LTI5YTctNGI4OS1hOGFjLTcyYjFmN2FhM2U5MiIsImlhdCI6MTYyMjgyODEyOCwiaXNzIjoiYXBpIiwic3ViIjoiMmQ5OTYyMDctYTkxMS00MDVlLWI1OTMtMTI1NjQzZmRiYzc4IiwiZW1haWwiOiJwZXBAdGVzdC5jb20iLCJ1c2VybmFtZSI6InBlcHNtb290aCIsIm5hbWUiOiJQZXAgU21vb3RoIiwiZ2l2ZW5fbmFtZSI6IlBlcCIsImZhbWlseV9uYW1lIjoiU21vb3RoIiwibmlja25hbWUiOiJQZXAiLCJwaWN0dXJlIjoiIn0.Zh35EwTtHMK0j0rLe_ZSt1eJZpy2lL11Ig0riPLUSfs",
"refreshToken": "NzQzNDE2NzUtMjU1Ni00M2VlLWE0NzItZTMxMjM4M2NjNzdi",
"tokenType": "bearer",
"expiresIn": 3600,
"refreshExpiresIn": 604800
}
$ grpcurl -plaintext \
-rpc-header "authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MjI4MzE3MjgsImp0aSI6IjA1NDcyOTU2LTI5YTctNGI4OS1hOGFjLTcyYjFmN2FhM2U5MiIsImlhdCI6MTYyMjgyODEyOCwiaXNzIjoiYXBpIiwic3ViIjoiMmQ5OTYyMDctYTkxMS00MDVlLWI1OTMtMTI1NjQzZmRiYzc4IiwiZW1haWwiOiJwZXBAdGVzdC5jb20iLCJ1c2VybmFtZSI6InBlcHNtb290aCIsIm5hbWUiOiJQZXAgU21vb3RoIiwiZ2l2ZW5fbmFtZSI6IlBlcCIsImZhbWlseV9uYW1lIjoiU21vb3RoIiwibmlja25hbWUiOiJQZXAiLCJwaWN0dXJlIjoiIn0.Zh35EwTtHMK0j0rLe_ZSt1eJZpy2lL11Ig0riPLUSfs" \
localhost:8080 template.AuthService/Me
{
"id": "05472956-29a7-4b89-a8ac-72b1f7aa3e92",
"email": "[email protected]",
"username": "pepsmooth",
"givenName": "Pep",
"familyName": "Smooth",
"nickname": "Pep"
}
$ grpcurl -plaintext \
-d '{ "grant_type": "refresh_token", "username": "pepsmooth", "refresh_token": "NzQzNDE2NzUtMjU1Ni00M2VlLWE0NzItZTMxMjM4M2NjNzdi"}' \
localhost:8080 template.AuthService/Token
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MjI4MzE4ODEsImp0aSI6IjM1ODUyYjZlLTdlNmMtNDI4MS1hMzAxLTI3ZWY2YTk5NGY2YyIsImlhdCI6MTYyMjgyODI4MSwiaXNzIjoiYXBpIiwic3ViIjoiMmQ5OTYyMDctYTkxMS00MDVlLWI1OTMtMTI1NjQzZmRiYzc4IiwiZW1haWwiOiJwZXBAdGVzdC5jb20iLCJ1c2VybmFtZSI6InBlcHNtb290aCIsIm5hbWUiOiJQZXAgU21vb3RoIiwiZ2l2ZW5fbmFtZSI6IlBlcCIsImZhbWlseV9uYW1lIjoiU21vb3RoIiwibmlja25hbWUiOiJQZXAiLCJwaWN0dXJlIjoiIn0.VtdCVFwd2S9DJGTjmE0sYSWOYl9eZs1qg-5F9444m6M",
"refreshToken": "ZmZhNGU3MTMtY2YxOS00ODQ5LWE0YWUtOGU2NTBjMjliYWZm",
"tokenType": "bearer",
"expiresIn": 3600,
"refreshExpiresIn": 604800
}
$ grpcurl -plaintext \
-d '{ "grant_type": "password", "username": "pepsmooth", "password": "mypass"}' \
localhost:8080 template.AuthService/Token
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MjI4MzI1MTYsImp0aSI6IjFhYjk0YWEwLTJjOWMtNDY2NC1hODM5LWM2ZWY1ODYzM2RkNiIsImlhdCI6MTYyMjgyODkxNiwiaXNzIjoiYXBpIiwic3ViIjoiMmQ5OTYyMDctYTkxMS00MDVlLWI1OTMtMTI1NjQzZmRiYzc4IiwiZW1haWwiOiJwZXBAdGVzdC5jb20iLCJ1c2VybmFtZSI6InBlcHNtb290aCIsIm5hbWUiOiJQZXAgU21vb3RoIiwiZ2l2ZW5fbmFtZSI6IlBlcCIsImZhbWlseV9uYW1lIjoiU21vb3RoIiwibmlja25hbWUiOiJQZXAiLCJwaWN0dXJlIjoiIn0.esN_ipmbbITUlKSwQIo2rgbJHIe-1MTOsYNDcL1-K5o",
"refreshToken": "MWZmN2E3Y2EtNTE0Ni00Y2E3LTg2M2QtZWU5OTI4NGQzMTMy",
"tokenType": "bearer",
"expiresIn": 3600,
"refreshExpiresIn": 604800
}
$ grpcurl -plaintext \
-rpc-header "authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MjI4MzE3MjgsImp0aSI6IjA1NDcyOTU2LTI5YTctNGI4OS1hOGFjLTcyYjFmN2FhM2U5MiIsImlhdCI6MTYyMjgyODEyOCwiaXNzIjoiYXBpIiwic3ViIjoiMmQ5OTYyMDctYTkxMS00MDVlLWI1OTMtMTI1NjQzZmRiYzc4IiwiZW1haWwiOiJwZXBAdGVzdC5jb20iLCJ1c2VybmFtZSI6InBlcHNtb290aCIsIm5hbWUiOiJQZXAgU21vb3RoIiwiZ2l2ZW5fbmFtZSI6IlBlcCIsImZhbWlseV9uYW1lIjoiU21vb3RoIiwibmlja25hbWUiOiJQZXAiLCJwaWN0dXJlIjoiIn0.Zh35EwTtHMK0j0rLe_ZSt1eJZpy2lL11Ig0riPLUSfs" \
-d '{"text": "finish everything", "author": "pep"}' \
localhost:8080 template.TodoService/Create
{
"id": "6978690c-5a6d-4771-9182-81aaf6fc333e",
"text": "finish everything",
"author": "pep",
"timestamp": "2021-05-28 17:19:29.5131457 +0000 UTC m=+160.747979501"
}
$ grpcurl -plaintext \
-rpc-header "authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MjI4MzE3MjgsImp0aSI6IjA1NDcyOTU2LTI5YTctNGI4OS1hOGFjLTcyYjFmN2FhM2U5MiIsImlhdCI6MTYyMjgyODEyOCwiaXNzIjoiYXBpIiwic3ViIjoiMmQ5OTYyMDctYTkxMS00MDVlLWI1OTMtMTI1NjQzZmRiYzc4IiwiZW1haWwiOiJwZXBAdGVzdC5jb20iLCJ1c2VybmFtZSI6InBlcHNtb290aCIsIm5hbWUiOiJQZXAgU21vb3RoIiwiZ2l2ZW5fbmFtZSI6IlBlcCIsImZhbWlseV9uYW1lIjoiU21vb3RoIiwibmlja25hbWUiOiJQZXAiLCJwaWN0dXJlIjoiIn0.Zh35EwTtHMK0j0rLe_ZSt1eJZpy2lL11Ig0riPLUSfs" \
localhost:8080 template.TodoService/ListAll
{
"todos": [
{
"id": "6978690c-5a6d-4771-9182-81aaf6fc333e",
"text": "finish everything",
"author": "pep",
"timestamp": "2021-05-28 17:19:29.5131457 +0000 UTC m=+160.747979501"
}
]
}
grpc-gateway reverse proxy will setup HTTP/1 endpoints for each gRPC method
$ curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MjI4MzI1MTYsImp0aSI6IjFhYjk0YWEwLTJjOWMtNDY2NC1hODM5LWM2ZWY1ODYzM2RkNiIsImlhdCI6MTYyMjgyODkxNiwiaXNzIjoiYXBpIiwic3ViIjoiMmQ5OTYyMDctYTkxMS00MDVlLWI1OTMtMTI1NjQzZmRiYzc4IiwiZW1haWwiOiJwZXBAdGVzdC5jb20iLCJ1c2VybmFtZSI6InBlcHNtb290aCIsIm5hbWUiOiJQZXAgU21vb3RoIiwiZ2l2ZW5fbmFtZSI6IlBlcCIsImZhbWlseV9uYW1lIjoiU21vb3RoIiwibmlja25hbWUiOiJQZXAiLCJwaWN0dXJlIjoiIn0.esN_ipmbbITUlKSwQIo2rgbJHIe-1MTOsYNDcL1-K5o" \
-X GET localhost:8080/auth/me | json_pp
{
"username" : "pepsmooth",
"nickname" : "Pep",
"id" : "1ab94aa0-2c9c-4664-a839-c6ef58633dd6",
"family_name" : "Smooth",
"email" : "[email protected]",
"given_name" : "Pep"
}
$ curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MjI4MzI1MTYsImp0aSI6IjFhYjk0YWEwLTJjOWMtNDY2NC1hODM5LWM2ZWY1ODYzM2RkNiIsImlhdCI6MTYyMjgyODkxNiwiaXNzIjoiYXBpIiwic3ViIjoiMmQ5OTYyMDctYTkxMS00MDVlLWI1OTMtMTI1NjQzZmRiYzc4IiwiZW1haWwiOiJwZXBAdGVzdC5jb20iLCJ1c2VybmFtZSI6InBlcHNtb290aCIsIm5hbWUiOiJQZXAgU21vb3RoIiwiZ2l2ZW5fbmFtZSI6IlBlcCIsImZhbWlseV9uYW1lIjoiU21vb3RoIiwibmlja25hbWUiOiJQZXAiLCJwaWN0dXJlIjoiIn0.esN_ipmbbITUlKSwQIo2rgbJHIe-1MTOsYNDcL1-K5o" \
-X GET localhost:8080/todos | json_pp
{
"todos": [
{
"id": "6978690c-5a6d-4771-9182-81aaf6fc333e",
"text": "finish everything",
"author": "pep",
"timestamp": "2021-05-28 17:19:29.5131457 +0000 UTC m=+160.747979501"
}
]
}
Navigate to http://localhost:8080/graphiql to interact with the GraphQL playground
mutation Signup {
signup(
input: {
email: "[email protected]",
username: "pepsmooth",
password: "mypass",
givenName: "Pep",
familyName: "Smooth"
}
) {
accessToken
refreshToken
tokenType
expires
}
}