-
-
Save suntong/7b91b6edc55bc692324497617d6e02ae to your computer and use it in GitHub Desktop.
[v0.1.0] Golang + Fiber Boilerplate with a single command `./newGoAPI.sh` | Note: maybe you have to run `chmod a+x newGoAPI.sh` first
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
echo "==================================" | |
echo "Welcome to Golang + Fiber Project Creator v0.1.0" | |
echo "==================================" | |
echo "For this script, The user have to input the project name (this will use to be the go module name and the project name." | |
echo "" | |
read -n 1 -s -r -p "Press any key to continue" | |
echo "\n" | |
read -p "Enter project name: " project_name | |
[ -d $project_name ] && >&2 echo "\033[31mDirectory is Exists.\033[0m" && exit 1 || mkdir $project_name | |
echo "Processing..." | |
cd $project_name | |
# Step 1: Create Directory | |
echo 'Step 1/3: Create directories' | |
mkdir -p api config model repository service tools | |
# Step 2: Create files | |
echo 'Step 2/3: Create files & content' | |
#### API #### | |
echo "\tCreating 'api/api.go'" | |
cd api && cat > api.go << EOF | |
package api | |
import ( | |
"strings" | |
"$project_name/model" | |
"$project_name/service" | |
"github.com/gofiber/fiber/v2" | |
"github.com/rs/zerolog/log" | |
) | |
func BadRequestResponse(c *fiber.Ctx, msg string) error { // 400 | |
log.Error().Msg("Bad Request") | |
if msg == "" { | |
msg = "Please input correct field" | |
} | |
res := model.ResponseMessageStatus{ | |
Status: "bad_request", | |
ResponseMessage: msg, | |
} | |
return c.Status(fiber.StatusBadRequest).JSON(res) | |
} | |
func UnauthorizedResponse(c *fiber.Ctx, msg string) error { // 401 | |
log.Error().Msg("Unauthorized") | |
if msg == "" { | |
msg = "Unauthorized" | |
} | |
res := model.ResponseMessageStatus{ | |
Status: "unauthorized", | |
ResponseMessage: msg, | |
} | |
return c.Status(fiber.StatusUnauthorized).JSON(res) | |
} | |
func ForbiddenResponse(c *fiber.Ctx, msg string) error { // 403 | |
log.Error().Msg("Forbidden") | |
if msg == "" { | |
msg = "Forbidden" | |
} | |
res := model.ResponseMessageStatus{ | |
Status: "forbidden", | |
ResponseMessage: msg, | |
} | |
return c.Status(fiber.StatusForbidden).JSON(res) | |
} | |
func NotFoundResponse(c *fiber.Ctx, msg string) error { // 404 | |
log.Error().Msg("Not Found") | |
if msg == "" { | |
msg = "Not Found Data" | |
} | |
res := model.ResponseMessageStatus{ | |
Status: "not_found", | |
ResponseMessage: msg, | |
} | |
return c.Status(fiber.StatusNotFound).JSON(res) | |
} | |
func InternalServerError(c *fiber.Ctx, msg string) error { // 500 | |
log.Error().Msg("Internal Server Error") | |
if msg == "" { | |
msg = "Internal Server Error" | |
} | |
res := model.ResponseMessageStatus{ | |
Status: "internal_server_error", | |
ResponseMessage: msg, | |
} | |
return c.Status(fiber.StatusInternalServerError).JSON(res) | |
} | |
const ( | |
contentTypeValue = "application/json" | |
) | |
func Health(c *fiber.Ctx) error { | |
if c.Method() != fiber.MethodGet { | |
return fiber.ErrMethodNotAllowed | |
} | |
log.Info().Msg("---------- Health ----------") | |
c.Accepts(contentTypeValue) | |
serviceData := service.HealthCheck() | |
serviceData.Timestamp = service.GetTimestamp() | |
return c.Status(fiber.StatusOK).JSON(serviceData) | |
} | |
func IsError(c *fiber.Ctx, err error) bool { | |
if err == nil { | |
return false | |
} | |
log.Err(err) | |
if strings.Contains(err.Error(), "Not Found") { | |
NotFoundResponse(c, err.Error()) | |
} else { | |
BadRequestResponse(c, err.Error()) | |
} | |
return true | |
} | |
EOF | |
cd .. | |
#### Configuration #### | |
echo "\tCreating 'config/config.dev.json'" | |
cd config && cat > config.dev.json << EOF | |
{ | |
"ProjectName": "", | |
"ProjectDescription": "", | |
"Version": "0.1.0", | |
"ENV": "dev", | |
"TimeZone": "Asia/Bangkok", | |
"Listening": { | |
"IP": "localhost", | |
"Port": "3000" | |
} | |
} | |
EOF | |
cd .. | |
#### Model #### | |
echo "\tCreating 'model/model.go'" | |
cd model && cat > model.go << EOF | |
package model | |
type Health struct { | |
Name string \`json:"name"\` | |
Version string \`json:"version"\` | |
Status string \`json:"status"\` | |
ENV string \`json:"env"\` | |
Timestamp string \`json:"timestamp"\` | |
} | |
type ResponseMessageStatus struct { | |
Status string \`json:"response_status"\` | |
ResponseMessage string \`json:"response_message"\` | |
} | |
type ResponseMessageStatusData struct { | |
Status string \`json:"response_status"\` | |
Message string \`json:"response_message"\` | |
Data interface{} \`json:"response_data"\` | |
Timestamp string \`json:"response_timestamp"\` | |
} | |
EOF | |
cd .. | |
#### Repository #### | |
echo "\tCreating 'repository/repository.go'" | |
cd repository && echo "package repository" > repository.go && cd .. | |
#### Service #### | |
echo "\tCreating 'service/service.go'" | |
cd service && cat > service.go << EOF | |
package service | |
import ( | |
"fmt" | |
"os" | |
"time" | |
"$project_name/model" | |
"github.com/rs/zerolog/log" | |
"github.com/spf13/viper" | |
) | |
// ################# | |
// ## <UTILITIES> ## | |
// ################# | |
func SetupConfig(configPath string) { | |
if os.Getenv("ENV") == "" { | |
panic(fmt.Errorf("πππππ PLEASE SET \`ENV\` ex. \`export ENV=dev\` πππππ")) | |
} | |
viper.SetConfigType("json") | |
viper.SetConfigName("config." + os.Getenv("ENV")) | |
viper.AddConfigPath(configPath) | |
err := viper.ReadInConfig() | |
if err != nil { | |
panic(fmt.Errorf("Fatal error config file: %s \n", err)) | |
} | |
viper.Debug() | |
} | |
func GetTimestamp() string { | |
date := time.Now() | |
// location, err := time.LoadLocation(viper.GetString("TimeZone")) | |
location, err := time.LoadLocation("Asia/Bangkok") | |
if err != nil { | |
log.Error().Err(err).Msg("Get Timestamp Error") | |
return date.Format("2006-01-02T15:04:05Z") | |
} | |
return date.In(location).Format("2006-01-02T15:04:05Z") | |
} | |
// ################## | |
// ## </UTILITIES> ## | |
// ################## | |
func HealthCheck() model.Health { | |
var HealthStatus model.Health | |
HealthStatus.Name = viper.GetString("ProjectName") | |
HealthStatus.Status = "I'm OK." | |
HealthStatus.Version = viper.GetString("Version") | |
HealthStatus.ENV = viper.GetString("ENV") | |
return HealthStatus | |
} | |
EOF | |
cd .. | |
#### Tools #### | |
echo "\tCreating 'tools/Health.http'" | |
cd tools && cat > Health.http << EOF | |
### $project_name Health GET | |
GET http://localhost:3000/api/v1/health HTTP/1.1 | |
### $project_name Health POST | |
POST http://localhost:3000/api/v1/health HTTP/1.1 | |
content-type: application/json | |
{} | |
EOF | |
cd .. | |
#### Air Configuration #### | |
echo "\tCreating '.air.conf'" | |
cat > .air.conf << EOF | |
root = "." | |
tmp_dir = "tmp" | |
[build] | |
delay = 500 # ms | |
EOF | |
#### ENV #### | |
echo "\tCreating '.env'" | |
cat > .env << EOF | |
TAG=0.1.0 | |
ENV=dev | |
WORKSPACE_NAME=$project_name # PLEASE EDIT | |
PROJECT_NAME=$project_name | |
EOF | |
#### Gitignore #### | |
echo "\tCreating '.gitignore'" | |
cat > .gitignore << EOF | |
# 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/ | |
/vendor/ | |
/Godeps/ | |
# Directory Generated by air | |
tmp/ | |
EOF | |
#### Main #### | |
echo "\tCreating 'main.go'" | |
cat > main.go << EOF | |
package main | |
import ( | |
"$project_name/api" | |
"$project_name/service" | |
"github.com/gofiber/fiber/v2" | |
"github.com/gofiber/fiber/v2/middleware/cors" | |
"github.com/rs/zerolog" | |
"github.com/rs/zerolog/log" | |
"github.com/spf13/viper" | |
) | |
var nextHandlerAPI = func(c *fiber.Ctx) error { | |
log.Info().Msg("Called API") | |
return c.Next() | |
} | |
var nextHandlerV1 = func(c *fiber.Ctx) error { | |
log.Info().Msg("Called V1") | |
return c.Next() | |
} | |
func init() { | |
service.SetupConfig("./config") | |
// UNIX Time is faster and smaller than most timestamps | |
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix | |
} | |
func main() { | |
app := fiber.New() | |
app.Use(cors.New()) | |
// app.Static("/docs", "./docs") | |
apiGroup := app.Group("/api", nextHandlerAPI) // /api | |
v1 := apiGroup.Group("/v1", nextHandlerV1) // /api/v1 | |
// ## GET METHOD ## | |
v1.Get("/health", api.Health) // /api/v1/health | |
PORT := viper.GetString("Listening.Port") | |
app.Listen(":" + PORT) | |
} | |
EOF | |
#### Makefile #### | |
echo "\tCreating 'Makefile'" | |
cat > Makefile << EOF | |
include .env | |
export | |
# init: | |
# git config core.hooksPath .githooks | |
hello: | |
echo "Hello" | |
check-binpath: | |
if echo \$\$PATH | tr ':' '\n' | grep -x -c -q "\$\$HOME/go/bin" ; then >& /dev/null ; else echo "\033[0;33m" '\n\nPlease run \`export PATH=\$\$PATH:\$\$HOME/go/bin\` before run command\n\n' "\033[0m" && exit 1 ; fi | |
check-swagger: check-binpath | |
which swag || go get -u github.com/swaggo/swag/cmd/swag && swag --version | |
genswag: check-swagger | |
swag init | |
run: | |
chmod a+x run.sh | |
./run.sh | |
air: ## HOT RELOAD MODE ## | |
air | |
test: | |
go test ./api ./repository ./service -coverprofile coverage.unit.out -run 'Test([^I]|I[^n]|In[^t]|Int[^e]|Inte[^g]|Integ[^r]|Integr[^a]|Integra[^t]|Integrat[^i]|Integrati[^o]|Integratio[^n]).*' | |
test-api: | |
go test ./api -coverprofile coverage.unit.out -run 'Test([^I]|I[^n]|In[^t]|Int[^e]|Inte[^g]|Integ[^r]|Integr[^a]|Integra[^t]|Integrat[^i]|Integrati[^o]|Integratio[^n]).*' | |
test-repository: | |
go test ./repository -coverprofile coverage.unit.out -run 'Test([^I]|I[^n]|In[^t]|Int[^e]|Inte[^g]|Integ[^r]|Integr[^a]|Integra[^t]|Integrat[^i]|Integrati[^o]|Integratio[^n]).*' | |
test-service: | |
go test ./service -coverprofile coverage.unit.out -run 'Test([^I]|I[^n]|In[^t]|Int[^e]|Inte[^g]|Integ[^r]|Integr[^a]|Integra[^t]|Integrat[^i]|Integrati[^o]|Integratio[^n]).*' | |
merge-coverprofile: | |
go get github.com/wadey/gocovmerge | |
gocovmerge ./coverage.* > coverage.out | |
build: | |
go build -o bin/main main.go | |
build-binary: | |
go build -o ./bin/\${PROJECT_NAME} main.go | |
build-binary-old: | |
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags="-w -s" -o ./bin/\${PROJECT_NAME} main.go | |
build-docker-image: | |
docker build --build-arg APP_VERSION=\${TAG} -f Dockerfile.scratch . -t bluecurve.int.baac.or.th/\${WORKSPACE_NAME}/\${PROJECT_NAME}:\${TAG} | |
push-docker-image: | |
docker login bluecurve.int.baac.or.th -u \${HARBOR_USER} -p \${HARBOR_PW} | |
docker push bluecurve.int.baac.or.th/\${WORKSPACE_NAME}/\${PROJECT_NAME}:\${TAG} | |
scan-docker-image: | |
docker run --rm -v /var/cache:/root/.cache/ -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy --light --severity MEDIUM,HIGH,CRITICAL --exit-code 1 --ignore-unfixed --no-progress bluecurve.int.baac.or.th/\${WORKSPACE_NAME}/\${PROJECT_NAME}:\${TAG} | |
docker login bluecurve.int.baac.or.th -u \${HARBOR_USER} -p \${HARBOR_PW} | |
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock bluecurve.int.baac.or.th/cicd/dockle:0.2.4 bluecurve.int.baac.or.th/\${WORKSPACE_NAME}/\${PROJECT_NAME}:\${TAG} | |
EOF | |
#### Readme File #### | |
echo "\tCreating 'README.md'" | |
cat > README.md << EOF | |
# $project_name | |
## π About | |
$project_name | |
## π¦ Built With | |
- [x] Golang with GoFiber | |
- [x] viper β Configuration Management | |
- [x] Zerolog - Log Management | |
## β Structure | |
\`\`\`mermaid | |
graph LR; | |
Requester-->$project_name | |
$project_name-->Databases | |
\`\`\` | |
## π· Versions | |
v0.1.0 | |
- Initialized | |
- [New] Health API (\`/api/v1/health\`) | |
## π Features | |
- \`/api/v1/health\` | for get health status | |
## π Test Cases | |
No Data | |
## β Get Started | |
1. Clone project | |
\`\`\`bash | |
git clone https://ipanda.it.baac.or.th/[WORKSPACE_NAME]/$project_name.git | |
\`\`\` | |
2. Go to project folder | |
\`\`\`bash | |
cd $project_name | |
\`\`\` | |
3. Set up environment | |
\`\`\`bash | |
export ENV=dev | |
\`\`\` | |
4. Run project by command | |
\`\`\`shell | |
# Normal Mode | |
go run main.go | |
# Test Mode | |
go test ./... -v | |
# also works with run.sh | |
chmod a+x run.sh | |
./run.sh | |
# and also works with Make Command | |
make run | |
\`\`\` | |
EOF | |
#### Run Script #### | |
echo "\tCreating 'run.sh'" | |
cat > run.sh << EOF | |
#!/bin/sh | |
echo "Running..." | |
export ENV=dev | |
go run main.go | |
EOF | |
# Step 3: Go Mod Init | |
echo "Step 3/3: Go Mod Init" | |
go mod init $project_name > /dev/null 2>&1 | |
go mod tidy > /dev/null 2>&1 | |
echo "Finished, Happy Coding. :D" | |
## NOTE: using by command `./newGoAPI.sh` maybe you have to run `chmod a+x newGoAPI.sh` first |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment