Skip to content

Instantly share code, notes, and snippets.

@buildingwatsize
Created March 19, 2021 04:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save buildingwatsize/c387a2a31633d8e5b1ebac7f6200f936 to your computer and use it in GitHub Desktop.
Save buildingwatsize/c387a2a31633d8e5b1ebac7f6200f936 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
#!/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