From 6e4179731c4fc0f1ac9ecc7b1aace156ae52070f Mon Sep 17 00:00:00 2001 From: Janishar Ali Date: Tue, 25 Jun 2024 17:10:53 +0530 Subject: [PATCH] update readme and add env copier --- .extra/docs/goserve-banner.png | Bin 0 -> 11181 bytes .extra/docs/goserve-cover.png | Bin 0 -> 18840 bytes .tools/copy/envs.go | 53 +++++++++++++++ Makefile | 3 +- README.md | 119 +++++++++++++++++++++++++++------ 5 files changed, 153 insertions(+), 22 deletions(-) create mode 100644 .extra/docs/goserve-banner.png create mode 100644 .extra/docs/goserve-cover.png create mode 100644 .tools/copy/envs.go diff --git a/.extra/docs/goserve-banner.png b/.extra/docs/goserve-banner.png new file mode 100644 index 0000000000000000000000000000000000000000..b4655caf405a28322f5f43b4167fb7cb23c7a24e GIT binary patch literal 11181 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⪙^5-v!A}Xkwr{ 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 literal 0 HcmV?d00001 diff --git a/.tools/copy/envs.go b/.tools/copy/envs.go new file mode 100644 index 0000000..06b4893 --- /dev/null +++ b/.tools/copy/envs.go @@ -0,0 +1,53 @@ +package main + +import ( + "fmt" + "io" + "os" +) + +func copyFile(src, dst string) error { + // Open the source file for reading + sourceFile, err := os.Open(src) + if err != nil { + return fmt.Errorf("could not open source file: %w", err) + } + defer sourceFile.Close() + + // Create the destination file + destinationFile, err := os.Create(dst) + if err != nil { + return fmt.Errorf("could not create destination file: %w", err) + } + defer destinationFile.Close() + + // Copy the contents of the source file to the destination file + _, err = io.Copy(destinationFile, sourceFile) + if err != nil { + return fmt.Errorf("could not copy file: %w", err) + } + + // Ensure all data is flushed to the destination file + err = destinationFile.Sync() + if err != nil { + return fmt.Errorf("could not flush data to destination file: %w", err) + } + + return nil +} + +func main() { + err := copyFile(".env.example", ".env") + if err != nil { + fmt.Printf("Error copying file: %v\n", err) + return + } + + err = copyFile(".test.env.example", ".test.env") + if err != nil { + fmt.Printf("Error copying file: %v\n", err) + return + } + + fmt.Println("env files copied successfully!") +} diff --git a/Makefile b/Makefile index 91556af..ce9ec2b 100644 --- a/Makefile +++ b/Makefile @@ -15,8 +15,9 @@ test: cover: go test -cover ./... -keygen: +setup: go run .tools/rsa/keygen.go + go run .tools/copy/envs.go # make apigen ARGS="sample" apigen: diff --git a/README.md b/README.md index 7dfa3b4..62690b5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,75 @@ -# Go Backend Development - goserve +# goserve - Go Backend Architecture -## API Framework Design +[![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) + +![Banner](.extra/docs/goserve-banner.png) + +## Create A Blog Service + +This 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. + +# Framework +- Go +- Gin +- jwt +- mongodriver +- go-redis +- Validator +- Viper +- Crypto + +## Highlights +- API key support +- Token based Authentication +- Role based Authorization +- Unit Tests +- Integration Tests +- Modular codebase + +# Architecture +The 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. + +The 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. + +## Startup Flow +cmd/main → startup/server → module, mongo, redis, router → api/[feature]/middlewares → api/[feature]/controller -> api/[feature]/service, authentication, authorization → handlers → sender + +## API Structure +``` +Sample API +├── dto +│ └── create_sample.go +├── model +│ └── sample.go +├── controller.go +└── service.go +``` + +- Each feature API lives under `api` directory +- The request and response body is sent in the form of a DTO (Data Transfer Object) inside `dto` directory +- The database collection model lives inside `model` directory +- Controller is responsible for defining endpoints and corresponding handlers +- 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. + +# Project Directories +1. **api**: APIs code +2. **arch**: It provide framework and base implementation for creating the architecture +3. **cmd**: main function to start the program +4. **common**: code to be used in all the apis +5. **config**: load environment variables +6. **keys**: stores server pem files for token +7. **startup**: creates server and initializes database, redis, and router +8. **tests**: holds the integration tests +9. **utils**: contains utility functions + +## Helper/Optional Directories +1. **.extra**: mongo script for initialization inside docker, other web assets and documents +2. **.github**: CI for tests +3. **.tools**: api code, RSA key generator, and .env copier +4. **.vscode**: editor config and debug launch settings + + +## API Design ![Request-Response-Design](.extra/docs/api-structure.png) ## API DOC @@ -9,13 +78,23 @@ # Installation Instruction vscode is the recommended editor - dark theme -### Get the repo +### 1. Get the repo ```bash git clone https://github.com/unusualcodeorg/goserve.git ``` -### Run Docker Compose +### 2. Generate RSA Keys +``` +go run .tools/rsa/keygen.go +``` + +### 3. Create .env files +``` +go run .tools/copy/envs.go +``` + +### 4. Run Docker Compose - Install Docker and Docker Compose. [Find Instructions Here](https://docs.docker.com/install/). ```bash @@ -23,9 +102,9 @@ docker-compose up --build ``` - You will be able to access the api from http://localhost:8080 -### Run Tests +### 5. Run Tests ```bash -TODO +docker exec -t goserver go test -v ./... ``` If having any issue @@ -33,33 +112,31 @@ If having any issue - Make sure 27017 port is not occupied else change DB_PORT in **.env** file. - Make sure 6379 port is not occupied else change REDIS_PORT in **.env** file. -## Run on the local machine -Change the following hosts in the **.env** -- DB_HOST=localhost -- REDIS_HOST=localhost - -Best way to run this project is to use the vscode `Run and Debug` button. Scripts are available for debugging and template generation on vscode. - +# Run on the local machine ```bash go mod tidy ``` -or -```bash -make install -``` +Keep the docker container for `mongo` and `redis` running and **stop** the `goserve` docker container + +Change the following hosts in the **.env** and **.test.env** +- DB_HOST=localhost +- REDIS_HOST=localhost -### Running the app +Best way to run this project is to use the vscode `Run and Debug` button. Scripts are available for debugging and template generation on vscode. + +### Optional - Running the app from terminal ```bash go run cmd/main.go ``` -or +# Template +New 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] + ```bash -make run +go run .tools/apigen.go sample ``` - ## Find this project useful ? :heart: * Support it by clicking the :star: button on the upper right of this page. :v: