{"id":15154352,"url":"https://github.com/unusualcodeorg/goserve","last_synced_at":"2025-04-14T09:35:35.485Z","repository":{"id":246035484,"uuid":"810978990","full_name":"unusualcodeorg/goserve","owner":"unusualcodeorg","description":"goserve is a robust Go backend architecture. It offers a performant and scalable framework, emphasizing feature separation, clean code, and testability. Ideal for REST API development, goserve simplifies unit and integration testing, ensuring high-quality, production-ready applications with ease.","archived":false,"fork":false,"pushed_at":"2024-07-10T11:39:56.000Z","size":530,"stargazers_count":113,"open_issues_count":1,"forks_count":17,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-27T22:51:13.224Z","etag":null,"topics":["api","backend","blogging","development","devops","docker","docker-compose","framework","gin-gonic","go","golang","golang-package","goserver","jwt","mongo","programming","redis","rest","rest-api","server"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/unusualcodeorg.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-06-05T17:56:28.000Z","updated_at":"2025-03-27T06:51:20.000Z","dependencies_parsed_at":null,"dependency_job_id":"71416b2d-109c-4676-9e34-7e75431e7075","html_url":"https://github.com/unusualcodeorg/goserve","commit_stats":null,"previous_names":["unusualcodeorg/goserve"],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unusualcodeorg%2Fgoserve","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unusualcodeorg%2Fgoserve/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unusualcodeorg%2Fgoserve/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unusualcodeorg%2Fgoserve/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/unusualcodeorg","download_url":"https://codeload.github.com/unusualcodeorg/goserve/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248854727,"owners_count":21172406,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","backend","blogging","development","devops","docker","docker-compose","framework","gin-gonic","go","golang","golang-package","goserver","jwt","mongo","programming","redis","rest","rest-api","server"],"created_at":"2024-09-26T17:21:58.497Z","updated_at":"2025-04-14T09:35:35.447Z","avatar_url":"https://github.com/unusualcodeorg.png","language":"Go","readme":"# goserve - Go Backend Architecture\n\n[![Docker Compose CI](https://github.com/unusualcodeorg/goserve/actions/workflows/docker_compose.yml/badge.svg)](https://github.com/unusualcodeorg/goserve/actions/workflows/docker_compose.yml)\n[![Starter Project](https://img.shields.io/badge/Starter%20Project%20CLI-red?label=Get\u0026logo=go)](https://github.com/unusualcodeorg/goservegen)\n[![Download](https://img.shields.io/badge/Download-Starter%20Project%20Zip-green.svg)](https://github.com/unusualcodeorg/goservegen/raw/main/project.zip)\n\n![Banner](.extra/docs/goserve-banner.png)\n\n## Create A Blog Service \n\nThis project is a fully production-ready solution designed to implement best practices for building performant and secure backend REST API services. It provides a robust architectural framework to ensure consistency and maintain high code quality. The architecture emphasizes feature separation, facilitating easier unit and integration testing.\n\n## Framework\n- Go\n- Gin\n- jwt\n- mongodriver\n- go-redis\n- Validator\n- Viper\n- Crypto\n\n**Highlights**\n- API key support\n- Token based Authentication\n- Role based Authorization\n- Unit Tests\n- Integration Tests\n- Modular codebase\n\n## Architecture\nThe goal is to make each API independent from one another and only share services among them. This will make code reusable and reduce conflicts while working in a team. \n\nThe APIs will have separate directory based on the endpoint. Example `blog` and `blogs` will have seperate directory whereas `blog`, `blog/author`, and `blog/editor` will share common resources and will live inside same directory.\n\n### Startup Flow\ncmd/main → startup/server → module, mongo, redis, router → api/[feature]/middlewares → api/[feature]/controller -\u003e api/[feature]/service, authentication, authorization → handlers → sender\n\n### API Structure\n```\nSample API\n├── dto\n│   └── create_sample.go\n├── model\n│   └── sample.go\n├── controller.go\n└── service.go\n```\n\n- Each feature API lives under `api` directory\n- The request and response body is sent in the form of a DTO (Data Transfer Object) inside `dto` directory\n- The database collection model lives inside `model` directory\n- Controller is responsible for defining endpoints and corresponding handlers\n- Service is the main logic component and handles data. Controller interact with a service to process a request. A service can also interact with other services.\n \n## Project Directories\n1. **api**: APIs code \n2. **arch**: It provide framework and base implementation for creating the architecture\n3. **cmd**: main function to start the program\n4. **common**: code to be used in all the apis\n5. **config**: load environment variables\n6. **keys**: stores server pem files for token\n7. **startup**: creates server and initializes database, redis, and router\n8. **tests**: holds the integration tests\n9. **utils**: contains utility functions\n\n**Helper/Optional Directories**\n1. **.extra**: mongo script for initialization inside docker, other web assets and documents\n2. **.github**: CI for tests\n3. **.tools**: api code, RSA key generator, and .env copier\n4. **.vscode**: editor config and debug launch settings\n\n## API Design\n![Request-Response-Design](.extra/docs/request-flow.svg)\n\n### API DOC\n[![API Documentation](https://img.shields.io/badge/API%20Documentation-View%20Here-blue?style=for-the-badge)](https://documenter.getpostman.com/view/1552895/2sA3XWdefu)\n\n## Installation Instructions\nvscode is the recommended editor - dark theme \n\n**1. Get the repo**\n\n```bash\ngit clone https://github.com/unusualcodeorg/goserve.git\n```\n\n**2. Generate RSA Keys**\n```\ngo run .tools/rsa/keygen.go\n```\n\n**3. Create .env files**\n```\ngo run .tools/copy/envs.go \n```\n\n**4. Run Docker Compose**\n- Install Docker and Docker Compose. [Find Instructions Here](https://docs.docker.com/install/).\n\n```bash\ndocker-compose up --build\n```\n-  You will be able to access the api from http://localhost:8080\n\n**5. Run Tests**\n```bash\ndocker exec -t goserver go test -v ./...\n```\n\nIf having any issue\n- Make sure 8080 port is not occupied else change SERVER_PORT in **.env** file.\n- Make sure 27017 port is not occupied else change DB_PORT in **.env** file.\n- Make sure 6379 port is not occupied else change REDIS_PORT in **.env** file.\n\n## Run on the local machine\n```bash\ngo mod tidy\n```\n\nKeep the docker container for `mongo` and `redis` running and **stop** the `goserve` docker container\n\nChange the following hosts in the **.env** and **.test.env**\n- DB_HOST=localhost\n- REDIS_HOST=localhost\n\nBest way to run this project is to use the vscode `Run and Debug` button. Scripts are available for debugging and template generation on vscode.\n\n### Optional - Running the app from terminal\n```bash\ngo run cmd/main.go\n```\n\n## Template\nNew api creation can be done using command. `go run .tools/apigen.go [feature_name]`. This will create all the required skeleton files inside the directory api/[feature_name]\n\n```bash\ngo run .tools/apigen.go sample\n```\n\n## Read the Article to understand this project\n[How to Architect Good Go Backend REST API Services](https://medium.com/@janishar.ali/how-to-architecture-good-go-backend-rest-api-services-14cc4730c05b)\n\n## How to use this architecture in your project?\nYou can use [goservegen](https://github.com/unusualcodeorg/goservegen) CLI to generate starter project for this architecture. \n\u003e Check out the repo [github.com/unusualcodeorg/goservegen](https://github.com/unusualcodeorg/goservegen) for more information.\n\n## Documentation\nInformation about the framework\n\n### Model\n`api/sample/model/sample.go`\n\n```go\npackage model\n\nimport (\n  \"context\"\n  \"time\"\n\n  \"github.com/go-playground/validator/v10\"\n  \"github.com/unusualcodeorg/goserve/arch/mongo\"\n  \"go.mongodb.org/mongo-driver/bson\"\n  \"go.mongodb.org/mongo-driver/bson/primitive\"\n  mongod \"go.mongodb.org/mongo-driver/mongo\"\n)\n\nconst CollectionName = \"samples\"\n\ntype Sample struct {\n  ID        primitive.ObjectID `bson:\"_id,omitempty\" validate:\"-\"`\n  Field     string             `bson:\"field\" validate:\"required\"`\n  Status    bool               `bson:\"status\" validate:\"required\"`\n  CreatedAt time.Time          `bson:\"createdAt\" validate:\"required\"`\n  UpdatedAt time.Time          `bson:\"updatedAt\" validate:\"required\"`\n}\n\nfunc NewSample(field string) (*Sample, error) {\n  time := time.Now()\n  doc := Sample{\n    Field:     field,\n    Status:    true,\n    CreatedAt: time,\n    UpdatedAt: time,\n  }\n  if err := doc.Validate(); err != nil {\n    return nil, err\n  }\n  return \u0026doc, nil\n}\n\nfunc (doc *Sample) GetValue() *Sample {\n  return doc\n}\n\nfunc (doc *Sample) Validate() error {\n  validate := validator.New()\n  return validate.Struct(doc)\n}\n\nfunc (*Sample) EnsureIndexes(db mongo.Database) {\n  indexes := []mongod.IndexModel{\n    {\n      Keys: bson.D{\n        {Key: \"_id\", Value: 1},\n        {Key: \"status\", Value: 1},\n      },\n    },\n  }\n  \n  mongo.NewQueryBuilder[Sample](db, CollectionName).Query(context.Background()).CreateIndexes(indexes)\n}\n```\n\n#### Notes: The Model implements the interface \n`arch/mongo/database`\n\n```golang\ntype Document[T any] interface {\n  EnsureIndexes(Database)\n  GetValue() *T\n  Validate() error\n}\n``` \n\n### DTO\n`api/sample/dto/create_sample.go`\n\n```go\npackage dto\n\nimport (\n  \"fmt\"\n  \"time\"\n\n  \"github.com/go-playground/validator/v10\"\n  \"go.mongodb.org/mongo-driver/bson/primitive\"\n)\n\ntype InfoSample struct {\n  ID        primitive.ObjectID `json:\"_id\" binding:\"required\"`\n  Field     string             `json:\"field\" binding:\"required\"`\n  CreatedAt time.Time          `json:\"createdAt\" binding:\"required\"`\n}\n\nfunc EmptyInfoSample() *InfoSample {\n  return \u0026InfoSample{}\n}\n\nfunc (d *InfoSample) GetValue() *InfoSample {\n  return d\n}\n\nfunc (d *InfoSample) ValidateErrors(errs validator.ValidationErrors) ([]string, error) {\n  var msgs []string\n  for _, err := range errs {\n    switch err.Tag() {\n    case \"required\":\n      msgs = append(msgs, fmt.Sprintf(\"%s is required\", err.Field()))\n    case \"min\":\n      msgs = append(msgs, fmt.Sprintf(\"%s must be min %s\", err.Field(), err.Param()))\n    case \"max\":\n      msgs = append(msgs, fmt.Sprintf(\"%s must be max %s\", err.Field(), err.Param()))\n    default:\n      msgs = append(msgs, fmt.Sprintf(\"%s is invalid\", err.Field()))\n    }\n  }\n  return msgs, nil\n}\n```\n\n#### Notes: The DTO implements the interface \n`arch/network/interfaces.go`\n\n```golang\ntype Dto[T any] interface {\n  GetValue() *T\n  ValidateErrors(errs validator.ValidationErrors) ([]string, error)\n}\n``` \n\n### Service\n`api/sample/service.go`\n\n```go\npackage sample\n\nimport (\n  \"github.com/unusualcodeorg/goserve/api/sample/dto\"\n  \"github.com/unusualcodeorg/goserve/api/sample/model\"\n  \"github.com/unusualcodeorg/goserve/arch/mongo\"\n  \"github.com/unusualcodeorg/goserve/arch/network\"\n  \"github.com/unusualcodeorg/goserve/arch/redis\"\n  \"go.mongodb.org/mongo-driver/bson\"\n  \"go.mongodb.org/mongo-driver/bson/primitive\"\n)\n\ntype Service interface {\n  FindSample(id primitive.ObjectID) (*model.Sample, error)\n}\n\ntype service struct {\n  network.BaseService\n  sampleQueryBuilder mongo.QueryBuilder[model.Sample]\n  infoSampleCache    redis.Cache[dto.InfoSample]\n}\n\nfunc NewService(db mongo.Database, store redis.Store) Service {\n  return \u0026service{\n    BaseService:  network.NewBaseService(),\n    sampleQueryBuilder: mongo.NewQueryBuilder[model.Sample](db, model.CollectionName),\n    infoSampleCache: redis.NewCache[dto.InfoSample](store),\n  }\n}\n\nfunc (s *service) FindSample(id primitive.ObjectID) (*model.Sample, error) {\n  filter := bson.M{\"_id\": id}\n\n  msg, err := s.sampleQueryBuilder.SingleQuery().FindOne(filter, nil)\n  if err != nil {\n    return nil, err\n  }\n\n  return msg, nil\n}\n```\n\n#### Notes: The Service embeds the interface \n`arch/network/interfaces.go`\n\n```golang\ntype BaseService interface {\n  Context() context.Context\n}\n``` \n\n- Database Query: `mongo.QueryBuilder[model.Sample]` provide the methods to make common mongo queries for the model `model.Sample`\n- Redis Cache: `redis.Cache[dto.InfoSample]` provide the methods to make common redis queries for the DTO `dto.InfoSample`\n\n### Controller\n`api/sample/controller.go`\n\n```go\npackage sample\n\nimport (\n  \"github.com/gin-gonic/gin\"\n  \"github.com/unusualcodeorg/goserve/api/sample/dto\"\n  \"github.com/unusualcodeorg/goserve/common\"\n  coredto \"github.com/unusualcodeorg/goserve/arch/dto\"\n  \"github.com/unusualcodeorg/goserve/arch/network\"\n  \"github.com/unusualcodeorg/goserve/utils\"\n)\n\ntype controller struct {\n  network.BaseController\n  common.ContextPayload\n  service Service\n}\n\nfunc NewController(\n  authMFunc network.AuthenticationProvider,\n  authorizeMFunc network.AuthorizationProvider,\n  service Service,\n) network.Controller {\n  return \u0026controller{\n    BaseController: network.NewBaseController(\"/sample\", authMFunc, authorizeMFunc),\n    ContextPayload: common.NewContextPayload(),\n    service:  service,\n  }\n}\n\nfunc (c *controller) MountRoutes(group *gin.RouterGroup) {\n  group.GET(\"/id/:id\", c.getSampleHandler)\n}\n\nfunc (c *controller) getSampleHandler(ctx *gin.Context) {\n  mongoId, err := network.ReqParams(ctx, coredto.EmptyMongoId())\n  if err != nil {\n    c.Send(ctx).BadRequestError(err.Error(), err)\n    return\n  }\n\n  sample, err := c.service.FindSample(mongoId.ID)\n  if err != nil {\n    c.Send(ctx).NotFoundError(\"sample not found\", err)\n    return\n  }\n\n  data, err := utils.MapTo[dto.InfoSample](sample)\n  if err != nil {\n    c.Send(ctx).InternalServerError(\"something went wrong\", err)\n    return\n  }\n\n  c.Send(ctx).SuccessDataResponse(\"success\", data)\n}\n```\n\n#### Notes: The Controller implements the interface \n`arch/network/interfaces.go`\n\n```golang\ntype Controller interface {\n  BaseController\n  MountRoutes(group *gin.RouterGroup)\n}\n\ntype BaseController interface {\n  ResponseSender\n  Path() string\n  Authentication() gin.HandlerFunc\n  Authorization(role string) gin.HandlerFunc\n}\n\ntype ResponseSender interface {\n  Debug() bool\n  Send(ctx *gin.Context) SendResponse\n}\n\ntype SendResponse interface {\n  SuccessMsgResponse(message string)\n  SuccessDataResponse(message string, data any)\n  BadRequestError(message string, err error)\n  ForbiddenError(message string, err error)\n  UnauthorizedError(message string, err error)\n  NotFoundError(message string, err error)\n  InternalServerError(message string, err error)\n  MixedError(err error)\n}\n``` \n\n### Enable Controller In Module\n`startup/module.go`\n\n```go\nimport (\n  ...\n  \"github.com/unusualcodeorg/goserve/api/sample\"\n)\n\n...\n\nfunc (m *module) Controllers() []network.Controller {\n  return []network.Controller{\n    ...\n    sample.NewController(m.AuthenticationProvider(), m.AuthorizationProvider(), sample.NewService(m.DB, m.Store)),\n  }\n}\n```\n\n### Indexing (If Needed)\n`startup/indexes.go`\n\n```go\nimport (\n  ...\n  sample \"github.com/unusualcodeorg/goserve/api/sample/model\"\n)\n\nfunc EnsureDbIndexes(db mongo.Database) {\n  go mongo.Document[sample.Sample](\u0026sample.Sample{}).EnsureIndexes(db)\n  ...\n}\n```\n\n## Go Microservices Architecture using goserve\n`goserve` also provides `micro` package to build REST API microservices. Find the microservices version of this blog service project at [github.com/unusualcodeorg/gomicro](https://github.com/unusualcodeorg/gomicro)\n\n[Article - How to Create Microservices — A Practical Guide Using Go](https://medium.com/@janishar.ali/how-to-create-microservices-a-practical-guide-using-go-35445a821513)\n\n## Find this project useful ? :heart:\n* Support it by clicking the :star: button on the upper right of this page. :v:\n\n## More on YouTube channel - Unusual Code\nSubscribe to the YouTube channel `UnusualCode` for understanding the concepts used in this project:\n\n[![YouTube](https://img.shields.io/badge/YouTube-Subscribe-red?style=for-the-badge\u0026logo=youtube\u0026logoColor=white)](https://www.youtube.com/@unusualcode)\n\n## Contribution\nPlease feel free to fork it and open a PR.","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Funusualcodeorg%2Fgoserve","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Funusualcodeorg%2Fgoserve","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Funusualcodeorg%2Fgoserve/lists"}