From 2a3ae6d45da2404da5823078fa9cf005e77fd8f3 Mon Sep 17 00:00:00 2001
From: bayusamudra5502 <bayusamudra.55.02.com@gmail.com>
Date: Sat, 4 Feb 2023 15:27:14 +0700
Subject: [PATCH] feat: adding project structure

---
 .env                                |   5 +
 .gitignore                          |   2 +-
 Makefile                            |   6 +-
 di.go                               |  28 +++++
 docs/base.go                        |  13 +++
 docs/docs.go                        |  38 ++++++-
 docs/swagger.json                   |  38 ++++++-
 docs/swagger.yaml                   |  25 ++++-
 go.mod                              |   3 +
 go.sum                              |   6 ++
 handler/common/handler.go           |  13 +++
 handler/common/home.go              |  17 ++++
 handler/common/types.go             |   7 ++
 handler/di.go                       |  17 ++++
 handler/swagger/route.go            |   3 +
 handler/swagger/swag.go             |  20 ++++
 handler/swagger/types.go            |   8 ++
 main.go                             |  12 ++-
 middleware/cleanpath/cleanpath.go   |  13 +++
 middleware/cors/cors.go             |  22 ++++
 middleware/di.go                    |  34 +++++++
 middleware/log/log.go               |  81 +++++++++++++++
 middleware/middlewares.go           |  22 ++++
 middleware/recoverer/recover.go     |  51 ++++++++++
 middleware/register.go              |  41 ++++++++
 middleware/trailslash/trailslash.go |  13 +++
 middleware/type.go                  |  13 +++
 model/web/base_response.go          |   7 ++
 model/web/status.go                 |  35 +++++++
 repository/di.go                    |   3 +
 routes/common/route.go              |  22 ++++
 routes/di.go                        |  19 ++++
 routes/register.go                  |  27 +++++
 routes/routes.go                    |  15 +++
 routes/swagger/route.go             |  15 +++
 routes/type.go                      |  11 ++
 service/common/home.go              |   5 +
 service/common/service.go           |   3 +
 service/common/type.go              |   5 +
 service/di.go                       |  32 ++++++
 service/logger/formatter.go         |  28 +++++
 service/logger/hooks/hooks.go       |  21 ++++
 service/logger/hooks/reporter.go    |  33 ++++++
 service/logger/logger.go            |  50 +++++++++
 service/logger/method.go            |  17 ++++
 service/logger/type.go              |   8 ++
 service/reporter/logtail.go         | 153 ++++++++++++++++++++++++++++
 service/reporter/type.go            |  16 +++
 utils/app/app.go                    |  50 +++++++++
 utils/app/list.go                   |  71 +++++++++++++
 utils/app/start.go                  |  78 ++++++++++++++
 utils/app/type.go                   |   7 ++
 utils/app/version.go                |  33 ++++++
 utils/di.go                         |  36 +++++++
 utils/env/env.go                    |  46 +++++++++
 utils/httputil/impl.go              |   3 +
 utils/httputil/parse.go             |  11 ++
 utils/httputil/type.go              |   9 ++
 utils/httputil/write.go             |  16 +++
 utils/log/color.go                  |  27 +++++
 utils/log/output.go                 |  31 ++++++
 utils/log/type.go                   |   7 ++
 utils/res/data/ascii.art            |   8 ++
 utils/res/data/version              |   1 +
 utils/res/embed.go                  |  25 +++++
 utils/res/res.go                    |   6 ++
 utils/wrapper/type.go               |   8 ++
 utils/wrapper/wrapper.go            |  21 ++++
 68 files changed, 1561 insertions(+), 9 deletions(-)
 create mode 100644 .env
 create mode 100644 di.go
 create mode 100644 docs/base.go
 create mode 100644 handler/common/handler.go
 create mode 100644 handler/common/home.go
 create mode 100644 handler/common/types.go
 create mode 100644 handler/di.go
 create mode 100644 handler/swagger/route.go
 create mode 100644 handler/swagger/swag.go
 create mode 100644 handler/swagger/types.go
 create mode 100644 middleware/cleanpath/cleanpath.go
 create mode 100644 middleware/cors/cors.go
 create mode 100644 middleware/di.go
 create mode 100644 middleware/log/log.go
 create mode 100644 middleware/middlewares.go
 create mode 100644 middleware/recoverer/recover.go
 create mode 100644 middleware/register.go
 create mode 100644 middleware/trailslash/trailslash.go
 create mode 100644 middleware/type.go
 create mode 100644 model/web/base_response.go
 create mode 100644 model/web/status.go
 create mode 100644 repository/di.go
 create mode 100644 routes/common/route.go
 create mode 100644 routes/di.go
 create mode 100644 routes/register.go
 create mode 100644 routes/routes.go
 create mode 100644 routes/swagger/route.go
 create mode 100644 routes/type.go
 create mode 100644 service/common/home.go
 create mode 100644 service/common/service.go
 create mode 100644 service/common/type.go
 create mode 100644 service/di.go
 create mode 100644 service/logger/formatter.go
 create mode 100644 service/logger/hooks/hooks.go
 create mode 100644 service/logger/hooks/reporter.go
 create mode 100644 service/logger/logger.go
 create mode 100644 service/logger/method.go
 create mode 100644 service/logger/type.go
 create mode 100644 service/reporter/logtail.go
 create mode 100644 service/reporter/type.go
 create mode 100644 utils/app/app.go
 create mode 100644 utils/app/list.go
 create mode 100644 utils/app/start.go
 create mode 100644 utils/app/type.go
 create mode 100644 utils/app/version.go
 create mode 100644 utils/di.go
 create mode 100644 utils/env/env.go
 create mode 100644 utils/httputil/impl.go
 create mode 100644 utils/httputil/parse.go
 create mode 100644 utils/httputil/type.go
 create mode 100644 utils/httputil/write.go
 create mode 100644 utils/log/color.go
 create mode 100644 utils/log/output.go
 create mode 100644 utils/log/type.go
 create mode 100644 utils/res/data/ascii.art
 create mode 100644 utils/res/data/version
 create mode 100644 utils/res/embed.go
 create mode 100644 utils/res/res.go
 create mode 100644 utils/wrapper/type.go
 create mode 100644 utils/wrapper/wrapper.go

diff --git a/.env b/.env
new file mode 100644
index 0000000..df1ee32
--- /dev/null
+++ b/.env
@@ -0,0 +1,5 @@
+ENV=DEVELOPMENT
+LISTEN_ADDR=0.0.0.0
+PORT=8080
+LOGTAIL_TOKEN=
+HTTP_SEC_TIMEOUT=2
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 5a10374..7e2965e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,3 @@
 bin/
 tmp/
-app/wire_gen.go
+wire_gen.go
diff --git a/Makefile b/Makefile
index 88ebcf6..6b33651 100644
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,8 @@
 all: build
 
 dependency:
-	# @swag init
-	# @wire ./app
+	@swag init
+	@wire
 
 run: dependency
 	@go run .
@@ -11,7 +11,7 @@ build: dependency
 	@go build -o=bin/server.app .
 
 watch:
-	@air --build.cmd="make build" --build.bin="./bin/server.app" --build.exclude_dir="bin,tmp,docs" --build.exclude_file="app/wire_gen.go"
+	@air --build.cmd="make build" --build.bin="./bin/server.app" --build.exclude_dir="bin,tmp,docs" --build.exclude_file="wire_gen.go"
 
 test: dependency
 	@go test ./test/... -v
diff --git a/di.go b/di.go
new file mode 100644
index 0000000..25590b9
--- /dev/null
+++ b/di.go
@@ -0,0 +1,28 @@
+//go:build wireinject
+// +build wireinject
+
+package main
+
+import (
+	"github.com/google/wire"
+
+	"gitlab.informatika.org/ocw/ocw-backend/handler"
+	"gitlab.informatika.org/ocw/ocw-backend/middleware"
+	"gitlab.informatika.org/ocw/ocw-backend/routes"
+	"gitlab.informatika.org/ocw/ocw-backend/service"
+	"gitlab.informatika.org/ocw/ocw-backend/utils"
+
+	"gitlab.informatika.org/ocw/ocw-backend/utils/app"
+)
+
+func CreateServer() (app.Server, error) {
+	wire.Build(
+		utils.UtilSet,
+		handler.HandlerSet,
+		middleware.MiddlewareSet,
+		routes.RoutesSet,
+		service.ServiceSet,
+	)
+
+	return nil, nil
+}
diff --git a/docs/base.go b/docs/base.go
new file mode 100644
index 0000000..88b5e59
--- /dev/null
+++ b/docs/base.go
@@ -0,0 +1,13 @@
+package docs
+
+import (
+	"bytes"
+	_ "embed"
+)
+
+//go:embed swagger.json
+var jsonDefinition []byte
+
+func GetJsonSwagger() *bytes.Reader {
+	return bytes.NewReader(jsonDefinition)
+}
diff --git a/docs/docs.go b/docs/docs.go
index 6952370..9ac0165 100644
--- a/docs/docs.go
+++ b/docs/docs.go
@@ -14,7 +14,43 @@ const docTemplate = `{
     },
     "host": "{{.Host}}",
     "basePath": "{{.BasePath}}",
-    "paths": {}
+    "paths": {
+        "/": {
+            "get": {
+                "description": "Give server index page response",
+                "produces": [
+                    "application/json"
+                ],
+                "summary": "Index page",
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/web.BaseResponse"
+                        }
+                    }
+                }
+            }
+        }
+    },
+    "definitions": {
+        "web.BaseResponse": {
+            "type": "object",
+            "properties": {
+                "data": {},
+                "message": {
+                    "type": "string"
+                },
+                "status": {
+                    "type": "string",
+                    "enum": [
+                        "success",
+                        "failed"
+                    ]
+                }
+            }
+        }
+    }
 }`
 
 // SwaggerInfo holds exported Swagger Info so clients can modify it
diff --git a/docs/swagger.json b/docs/swagger.json
index ec416cd..c853049 100644
--- a/docs/swagger.json
+++ b/docs/swagger.json
@@ -3,5 +3,41 @@
     "info": {
         "contact": {}
     },
-    "paths": {}
+    "paths": {
+        "/": {
+            "get": {
+                "description": "Give server index page response",
+                "produces": [
+                    "application/json"
+                ],
+                "summary": "Index page",
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/web.BaseResponse"
+                        }
+                    }
+                }
+            }
+        }
+    },
+    "definitions": {
+        "web.BaseResponse": {
+            "type": "object",
+            "properties": {
+                "data": {},
+                "message": {
+                    "type": "string"
+                },
+                "status": {
+                    "type": "string",
+                    "enum": [
+                        "success",
+                        "failed"
+                    ]
+                }
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/docs/swagger.yaml b/docs/swagger.yaml
index b64379c..59dad6b 100644
--- a/docs/swagger.yaml
+++ b/docs/swagger.yaml
@@ -1,4 +1,27 @@
+definitions:
+  web.BaseResponse:
+    properties:
+      data: {}
+      message:
+        type: string
+      status:
+        enum:
+        - success
+        - failed
+        type: string
+    type: object
 info:
   contact: {}
-paths: {}
+paths:
+  /:
+    get:
+      description: Give server index page response
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/web.BaseResponse'
+      summary: Index page
 swagger: "2.0"
diff --git a/go.mod b/go.mod
index bc84614..dc2309a 100644
--- a/go.mod
+++ b/go.mod
@@ -6,13 +6,16 @@ require (
 	github.com/KyleBanks/depth v1.2.1 // indirect
 	github.com/PuerkitoBio/purell v1.1.1 // indirect
 	github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
+	github.com/caarlos0/env/v6 v6.10.1 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/go-chi/chi/v5 v5.0.8 // indirect
+	github.com/go-chi/cors v1.2.1 // indirect
 	github.com/go-openapi/jsonpointer v0.19.6 // indirect
 	github.com/go-openapi/jsonreference v0.20.2 // indirect
 	github.com/go-openapi/spec v0.20.8 // indirect
 	github.com/go-openapi/swag v0.22.3 // indirect
 	github.com/google/wire v0.5.0 // indirect
+	github.com/joho/godotenv v1.4.0 // indirect
 	github.com/josharian/intern v1.0.0 // indirect
 	github.com/mailru/easyjson v0.7.7 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
diff --git a/go.sum b/go.sum
index c58c519..2814bd2 100644
--- a/go.sum
+++ b/go.sum
@@ -4,12 +4,16 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN
 github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/caarlos0/env/v6 v6.10.1 h1:t1mPSxNpei6M5yAeu1qtRdPAK29Nbcf/n3G7x+b3/II=
+github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
 github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
+github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
+github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
 github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
 github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
 github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
@@ -33,6 +37,8 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
 github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
 github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8=
 github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
+github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
+github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
 github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
 github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
diff --git a/handler/common/handler.go b/handler/common/handler.go
new file mode 100644
index 0000000..4b44093
--- /dev/null
+++ b/handler/common/handler.go
@@ -0,0 +1,13 @@
+package common
+
+import (
+	"gitlab.informatika.org/ocw/ocw-backend/service/common"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/httputil"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/wrapper"
+)
+
+type CommonHandlerImpl struct {
+	common.CommonService
+	httputil.HttpUtil
+	wrapper.WrapperUtil
+}
diff --git a/handler/common/home.go b/handler/common/home.go
new file mode 100644
index 0000000..81fd72f
--- /dev/null
+++ b/handler/common/home.go
@@ -0,0 +1,17 @@
+package common
+
+import (
+	"net/http"
+)
+
+// Index godoc
+//
+//	@Summary			Index page
+//	@Description	Give server index page response
+//	@Produce			json
+//	@Success			200	{object}	web.BaseResponse
+//	@Router				/ [get]
+func (route CommonHandlerImpl) Home(w http.ResponseWriter, r *http.Request) {
+	payload := route.WrapperUtil.SuccessResponseWrap(route.CommonService.Home())
+	route.HttpUtil.WriteSuccessJson(w, payload)
+}
diff --git a/handler/common/types.go b/handler/common/types.go
new file mode 100644
index 0000000..f799be2
--- /dev/null
+++ b/handler/common/types.go
@@ -0,0 +1,7 @@
+package common
+
+import "net/http"
+
+type CommonHandler interface {
+	Home(w http.ResponseWriter, r *http.Request)
+}
diff --git a/handler/di.go b/handler/di.go
new file mode 100644
index 0000000..7c3cb64
--- /dev/null
+++ b/handler/di.go
@@ -0,0 +1,17 @@
+package handler
+
+import (
+	"github.com/google/wire"
+	"gitlab.informatika.org/ocw/ocw-backend/handler/common"
+	"gitlab.informatika.org/ocw/ocw-backend/handler/swagger"
+)
+
+var HandlerSet = wire.NewSet(
+	// Common
+	wire.Struct(new(common.CommonHandlerImpl), "*"),
+	wire.Bind(new(common.CommonHandler), new(*common.CommonHandlerImpl)),
+
+	// Swagger
+	wire.Struct(new(swagger.SwaggerHandlerImpl), "*"),
+	wire.Bind(new(swagger.SwaggerHandler), new(*swagger.SwaggerHandlerImpl)),
+)
diff --git a/handler/swagger/route.go b/handler/swagger/route.go
new file mode 100644
index 0000000..f5afd4f
--- /dev/null
+++ b/handler/swagger/route.go
@@ -0,0 +1,3 @@
+package swagger
+
+type SwaggerHandlerImpl struct{}
diff --git a/handler/swagger/swag.go b/handler/swagger/swag.go
new file mode 100644
index 0000000..a5fa451
--- /dev/null
+++ b/handler/swagger/swag.go
@@ -0,0 +1,20 @@
+package swagger
+
+import (
+	"net/http"
+
+	httpSwagger "github.com/swaggo/http-swagger"
+	"gitlab.informatika.org/ocw/ocw-backend/docs"
+)
+
+func (SwaggerHandlerImpl) Swagger(w http.ResponseWriter, r *http.Request) {
+	handler := httpSwagger.Handler()
+	handler(w, r)
+}
+
+func (SwaggerHandlerImpl) SwaggerFile(w http.ResponseWriter, r *http.Request) {
+	stream := docs.GetJsonSwagger()
+
+	w.WriteHeader(http.StatusOK)
+	stream.WriteTo(w)
+}
diff --git a/handler/swagger/types.go b/handler/swagger/types.go
new file mode 100644
index 0000000..dc08889
--- /dev/null
+++ b/handler/swagger/types.go
@@ -0,0 +1,8 @@
+package swagger
+
+import "net/http"
+
+type SwaggerHandler interface {
+	Swagger(w http.ResponseWriter, r *http.Request)
+	SwaggerFile(w http.ResponseWriter, r *http.Request)
+}
diff --git a/main.go b/main.go
index 54e1d9b..17e62fd 100644
--- a/main.go
+++ b/main.go
@@ -1,5 +1,13 @@
 package main
 
 func main() {
-	println("Hello, World")
-}
\ No newline at end of file
+	server, err := CreateServer()
+
+	if err != nil {
+		panic(err)
+	}
+
+	server.Version()
+	server.ListRoute()
+	server.Start()
+}
diff --git a/middleware/cleanpath/cleanpath.go b/middleware/cleanpath/cleanpath.go
new file mode 100644
index 0000000..7701e65
--- /dev/null
+++ b/middleware/cleanpath/cleanpath.go
@@ -0,0 +1,13 @@
+package cleanpath
+
+import (
+	"net/http"
+
+	"github.com/go-chi/chi/v5/middleware"
+)
+
+type CleanPathMiddleware struct{}
+
+func (CleanPathMiddleware) Handle(next http.Handler) http.Handler {
+	return middleware.CleanPath(next)
+}
diff --git a/middleware/cors/cors.go b/middleware/cors/cors.go
new file mode 100644
index 0000000..7be187f
--- /dev/null
+++ b/middleware/cors/cors.go
@@ -0,0 +1,22 @@
+package cors
+
+import (
+	"net/http"
+
+	"github.com/go-chi/cors"
+)
+
+type CorsMiddleware struct{}
+
+var corsHandler = cors.Handler(cors.Options{
+	AllowedOrigins:   []string{"http://*", "https://*"},
+	AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
+	AllowedHeaders:   []string{"*"},
+	ExposedHeaders:   []string{"Link"},
+	AllowCredentials: false,
+	MaxAge:           300,
+})
+
+func (CorsMiddleware) Handle(next http.Handler) http.Handler {
+	return corsHandler(next)
+}
diff --git a/middleware/di.go b/middleware/di.go
new file mode 100644
index 0000000..3e73df4
--- /dev/null
+++ b/middleware/di.go
@@ -0,0 +1,34 @@
+package middleware
+
+import (
+	"github.com/google/wire"
+	"gitlab.informatika.org/ocw/ocw-backend/middleware/cleanpath"
+	"gitlab.informatika.org/ocw/ocw-backend/middleware/cors"
+	"gitlab.informatika.org/ocw/ocw-backend/middleware/log"
+	"gitlab.informatika.org/ocw/ocw-backend/middleware/recoverer"
+	"gitlab.informatika.org/ocw/ocw-backend/middleware/trailslash"
+)
+
+var middlewareCollectionSet = wire.NewSet(
+	// Cleanpath
+	wire.Struct(new(cleanpath.CleanPathMiddleware), "*"),
+
+	// Cors
+	wire.Struct(new(cors.CorsMiddleware), "*"),
+
+	// Log
+	wire.Struct(new(log.RequestLogMiddleware), "*"),
+
+	// Recoverer
+	wire.Struct(new(recoverer.RecovererMiddleware), "*"),
+
+	// Trailslash
+	wire.Struct(new(trailslash.TrailSlashMiddleware), "*"),
+)
+
+var MiddlewareSet = wire.NewSet(
+	middlewareCollectionSet,
+
+	wire.Struct(new(AppMiddlewares), "*"),
+	wire.Bind(new(MiddlewareCollection), new(*AppMiddlewares)),
+)
diff --git a/middleware/log/log.go b/middleware/log/log.go
new file mode 100644
index 0000000..279d085
--- /dev/null
+++ b/middleware/log/log.go
@@ -0,0 +1,81 @@
+package log
+
+import (
+	"fmt"
+	"net/http"
+	"time"
+
+	chiMiddleware "github.com/go-chi/chi/v5/middleware"
+	"gitlab.informatika.org/ocw/ocw-backend/service/logger"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/log"
+)
+
+type RequestLogMiddleware struct {
+	LogUtil log.LogUtils
+	Logger  logger.Logger
+}
+
+var methodColor = map[string]log.Color{
+	"GET":    log.ForeGreen,
+	"POST":   log.ForeBlue,
+	"PUT":    log.ForeCyan,
+	"PATCH":  log.ForeMagenta,
+	"DELETE": log.ForeRed,
+}
+
+var statusColor = map[int]log.Color{
+	0:   log.ForeCyan,
+	200: log.ForeGreen,
+	300: log.ForeBlue,
+	400: log.ForeYellow,
+	500: log.ForeRed,
+}
+
+func (rl RequestLogMiddleware) colorizeMethod(method string) string {
+	val, ok := methodColor[method]
+
+	if ok {
+		return rl.LogUtil.ColoredOutput(method, val)
+	} else {
+		return rl.LogUtil.ColoredOutput(method, log.ForeWhite)
+	}
+}
+
+func (rl RequestLogMiddleware) colorizeCode(code int) string {
+	if code < 200 {
+		return rl.LogUtil.ColoredOutput(fmt.Sprint(code), statusColor[0])
+	} else if code < 300 {
+		return rl.LogUtil.ColoredOutput(fmt.Sprint(code), statusColor[200])
+	} else if code < 400 {
+		return rl.LogUtil.ColoredOutput(fmt.Sprint(code), statusColor[300])
+	} else if code < 500 {
+		return rl.LogUtil.ColoredOutput(fmt.Sprint(code), statusColor[400])
+	} else {
+		return rl.LogUtil.ColoredOutput(fmt.Sprint(code), statusColor[500])
+	}
+}
+
+func (rl RequestLogMiddleware) Handle(next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		startTime := time.Now()
+		ww := chiMiddleware.NewWrapResponseWriter(w, r.ProtoMajor)
+
+		defer func() {
+			delta := time.Since(startTime)
+			status := ww.Status()
+			path := r.URL.Path
+			method := r.Method
+
+			rl.Logger.Info(
+				fmt.Sprintf("Request %s %s %s (%dms)",
+					rl.colorizeCode(status),
+					rl.colorizeMethod(method),
+					path,
+					delta.Milliseconds(),
+				),
+			)
+		}()
+
+		next.ServeHTTP(ww, r)
+	})
+}
diff --git a/middleware/middlewares.go b/middleware/middlewares.go
new file mode 100644
index 0000000..b41cf45
--- /dev/null
+++ b/middleware/middlewares.go
@@ -0,0 +1,22 @@
+package middleware
+
+import (
+	"gitlab.informatika.org/ocw/ocw-backend/middleware/cleanpath"
+	"gitlab.informatika.org/ocw/ocw-backend/middleware/cors"
+	"gitlab.informatika.org/ocw/ocw-backend/middleware/log"
+	"gitlab.informatika.org/ocw/ocw-backend/middleware/recoverer"
+	"gitlab.informatika.org/ocw/ocw-backend/middleware/trailslash"
+	"gitlab.informatika.org/ocw/ocw-backend/service/logger"
+)
+
+type AppMiddlewares struct {
+	// Registered Middleware
+	recoverer.RecovererMiddleware
+	cors.CorsMiddleware
+	log.RequestLogMiddleware
+	trailslash.TrailSlashMiddleware
+	cleanpath.CleanPathMiddleware
+
+	// Utility
+	Logger logger.Logger
+}
diff --git a/middleware/recoverer/recover.go b/middleware/recoverer/recover.go
new file mode 100644
index 0000000..7c7b5b1
--- /dev/null
+++ b/middleware/recoverer/recover.go
@@ -0,0 +1,51 @@
+package recoverer
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"runtime/debug"
+	"strings"
+
+	"gitlab.informatika.org/ocw/ocw-backend/service/logger"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/wrapper"
+)
+
+type RecovererMiddleware struct {
+	Logger logger.Logger
+	wrapper.WrapperUtil
+}
+
+func (rm RecovererMiddleware) Handle(next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		defer func() {
+			if rec := recover(); rec != nil {
+				parser := json.NewEncoder(w)
+
+				w.WriteHeader(http.StatusInternalServerError)
+				payload := rm.WrapperUtil.ErrorResponseWrap("internal server error", nil)
+
+				err := parser.Encode(payload)
+
+				if err != nil {
+					rm.Logger.Error("Failed to parse error:" + err.Error())
+					rm.Logger.Error("")
+
+					return
+				}
+
+				stacks := strings.Split(string(debug.Stack()), "\n")
+				rm.Logger.Error("Some panic occured when processing request:")
+				rm.Logger.Error(fmt.Sprint(rec))
+				rm.Logger.Error("")
+
+				rm.Logger.Error("Stack Trace:")
+				for _, val := range stacks {
+					rm.Logger.Error(val)
+				}
+			}
+		}()
+
+		next.ServeHTTP(w, r)
+	})
+}
diff --git a/middleware/register.go b/middleware/register.go
new file mode 100644
index 0000000..998ed3e
--- /dev/null
+++ b/middleware/register.go
@@ -0,0 +1,41 @@
+package middleware
+
+import (
+	"reflect"
+)
+
+func (app *AppMiddlewares) Register() []Middleware {
+	reflectValue := reflect.ValueOf(app)
+
+	if reflectValue.Kind() == reflect.Ptr {
+		reflectValue = reflectValue.Elem()
+	}
+
+	var reflectType = reflectValue.Type()
+	collections := []Middleware{}
+
+	middlewareName := []string{}
+
+	for i := 0; i < reflectValue.NumField(); i++ {
+		field := reflectValue.Field(i)
+		handler, ok := field.Interface().(Middleware)
+
+		if !ok {
+			continue
+		}
+
+		middlewareName = append(middlewareName, reflectType.Field(i).Name)
+		collections = append(collections, handler)
+	}
+
+	if len(middlewareName) > 0 {
+		app.Logger.Info("Registered Middlewares:")
+		for _, middleware := range middlewareName {
+			app.Logger.Info("- " + middleware)
+		}
+	} else {
+		app.Logger.Info("No middleware registered")
+	}
+
+	return collections
+}
diff --git a/middleware/trailslash/trailslash.go b/middleware/trailslash/trailslash.go
new file mode 100644
index 0000000..7bcb14c
--- /dev/null
+++ b/middleware/trailslash/trailslash.go
@@ -0,0 +1,13 @@
+package trailslash
+
+import (
+	"net/http"
+
+	"github.com/go-chi/chi/v5/middleware"
+)
+
+type TrailSlashMiddleware struct{}
+
+func (TrailSlashMiddleware) Handle(next http.Handler) http.Handler {
+	return middleware.RedirectSlashes(next)
+}
diff --git a/middleware/type.go b/middleware/type.go
new file mode 100644
index 0000000..3c4b474
--- /dev/null
+++ b/middleware/type.go
@@ -0,0 +1,13 @@
+package middleware
+
+import (
+	"net/http"
+)
+
+type MiddlewareCollection interface {
+	Register() []Middleware
+}
+
+type Middleware interface {
+	Handle(next http.Handler) http.Handler
+}
diff --git a/model/web/base_response.go b/model/web/base_response.go
new file mode 100644
index 0000000..c752720
--- /dev/null
+++ b/model/web/base_response.go
@@ -0,0 +1,7 @@
+package web
+
+type BaseResponse struct {
+	Status  Status      `json:"status" swaggertype:"primitive,string" enums:"success,failed"`
+	Message string      `json:"message"`
+	Data    interface{} `json:"data"`
+}
diff --git a/model/web/status.go b/model/web/status.go
new file mode 100644
index 0000000..128aea5
--- /dev/null
+++ b/model/web/status.go
@@ -0,0 +1,35 @@
+package web
+
+import (
+	"errors"
+)
+
+type Status uint
+
+const (
+	Success Status = iota
+	Failed
+)
+
+func (s Status) MarshalJSON() ([]byte, error) {
+	switch s {
+	case Success:
+		return []byte("\"success\""), nil
+	case Failed:
+		return []byte("\"failed\""), nil
+	}
+
+	return nil, errors.New("unkown value")
+}
+
+func (s *Status) UnmarshalJSON(data []byte) error {
+	if string(data) == "\"success\"" {
+		*s = Success
+		return nil
+	} else if string(data) == "\"failed\"" {
+		*s = Failed
+		return nil
+	}
+
+	return errors.New("Unkown type of " + string(data))
+}
diff --git a/repository/di.go b/repository/di.go
new file mode 100644
index 0000000..f9a484e
--- /dev/null
+++ b/repository/di.go
@@ -0,0 +1,3 @@
+package repository
+
+// var RepositorySet = wire.Value()
diff --git a/routes/common/route.go b/routes/common/route.go
new file mode 100644
index 0000000..d57f0c1
--- /dev/null
+++ b/routes/common/route.go
@@ -0,0 +1,22 @@
+package common
+
+import (
+	"github.com/go-chi/chi/v5"
+	"gitlab.informatika.org/ocw/ocw-backend/service/common"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/httputil"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/wrapper"
+
+	commonHandler "gitlab.informatika.org/ocw/ocw-backend/handler/common"
+)
+
+type CommonRoutes struct {
+	common.CommonService
+	httputil.HttpUtil
+	wrapper.WrapperUtil
+	commonHandler.CommonHandler
+}
+
+func (cr CommonRoutes) Register(r chi.Router) {
+	r.Get("/", cr.CommonHandler.Home)
+	r.Get("/test", cr.CommonHandler.Home)
+}
diff --git a/routes/di.go b/routes/di.go
new file mode 100644
index 0000000..93a65f0
--- /dev/null
+++ b/routes/di.go
@@ -0,0 +1,19 @@
+package routes
+
+import (
+	"github.com/google/wire"
+	"gitlab.informatika.org/ocw/ocw-backend/routes/common"
+	"gitlab.informatika.org/ocw/ocw-backend/routes/swagger"
+)
+
+var routesCollectionSet = wire.NewSet(
+	wire.Struct(new(common.CommonRoutes), "*"),
+	wire.Struct(new(swagger.SwaggerRoutes), "*"),
+)
+
+var RoutesSet = wire.NewSet(
+	routesCollectionSet,
+
+	wire.Struct(new(AppRouter), "*"),
+	wire.Bind(new(RouteCollection), new(*AppRouter)),
+)
diff --git a/routes/register.go b/routes/register.go
new file mode 100644
index 0000000..e1dd4a4
--- /dev/null
+++ b/routes/register.go
@@ -0,0 +1,27 @@
+package routes
+
+import (
+	"reflect"
+)
+
+func (app *AppRouter) Register() []RouteGroup {
+	reflectValue := reflect.ValueOf(app)
+
+	if reflectValue.Kind() == reflect.Ptr {
+		reflectValue = reflectValue.Elem()
+	}
+
+	collections := []RouteGroup{}
+
+	for i := 0; i < reflectValue.NumField(); i++ {
+		handler, ok := reflectValue.Field(i).Interface().(RouteGroup)
+
+		if !ok {
+			continue
+		}
+
+		collections = append(collections, handler)
+	}
+
+	return collections
+}
diff --git a/routes/routes.go b/routes/routes.go
new file mode 100644
index 0000000..636fc01
--- /dev/null
+++ b/routes/routes.go
@@ -0,0 +1,15 @@
+package routes
+
+import (
+	"gitlab.informatika.org/ocw/ocw-backend/routes/common"
+	"gitlab.informatika.org/ocw/ocw-backend/routes/swagger"
+
+	"gitlab.informatika.org/ocw/ocw-backend/service/logger"
+)
+
+type AppRouter struct {
+	common.CommonRoutes
+	swagger.SwaggerRoutes
+
+	Logger logger.Logger
+}
diff --git a/routes/swagger/route.go b/routes/swagger/route.go
new file mode 100644
index 0000000..345aa7e
--- /dev/null
+++ b/routes/swagger/route.go
@@ -0,0 +1,15 @@
+package swagger
+
+import (
+	"github.com/go-chi/chi/v5"
+	"gitlab.informatika.org/ocw/ocw-backend/handler/swagger"
+)
+
+type SwaggerRoutes struct {
+	swagger.SwaggerHandler
+}
+
+func (sr SwaggerRoutes) Register(r chi.Router) {
+	r.Get("/docs", sr.SwaggerHandler.SwaggerFile)
+	r.Get("/docs/*", sr.SwaggerHandler.Swagger)
+}
diff --git a/routes/type.go b/routes/type.go
new file mode 100644
index 0000000..ee6a9db
--- /dev/null
+++ b/routes/type.go
@@ -0,0 +1,11 @@
+package routes
+
+import "github.com/go-chi/chi/v5"
+
+type RouteCollection interface {
+	Register() []RouteGroup
+}
+
+type RouteGroup interface {
+	Register(chi.Router)
+}
diff --git a/service/common/home.go b/service/common/home.go
new file mode 100644
index 0000000..d78a894
--- /dev/null
+++ b/service/common/home.go
@@ -0,0 +1,5 @@
+package common
+
+func (CommonServiceImpl) Home() string {
+	return "Server is running 🙂"
+}
diff --git a/service/common/service.go b/service/common/service.go
new file mode 100644
index 0000000..dd8d182
--- /dev/null
+++ b/service/common/service.go
@@ -0,0 +1,3 @@
+package common
+
+type CommonServiceImpl struct{}
diff --git a/service/common/type.go b/service/common/type.go
new file mode 100644
index 0000000..bb73f87
--- /dev/null
+++ b/service/common/type.go
@@ -0,0 +1,5 @@
+package common
+
+type CommonService interface {
+	Home() string
+}
diff --git a/service/di.go b/service/di.go
new file mode 100644
index 0000000..349e0a4
--- /dev/null
+++ b/service/di.go
@@ -0,0 +1,32 @@
+package service
+
+import (
+	"github.com/google/wire"
+	"gitlab.informatika.org/ocw/ocw-backend/service/common"
+	"gitlab.informatika.org/ocw/ocw-backend/service/logger"
+	"gitlab.informatika.org/ocw/ocw-backend/service/logger/hooks"
+	"gitlab.informatika.org/ocw/ocw-backend/service/reporter"
+)
+
+var ServiceSet = wire.NewSet(
+	// Common service
+	wire.NewSet(
+		wire.Struct(new(common.CommonServiceImpl), "*"),
+		wire.Bind(new(common.CommonService), new(*common.CommonServiceImpl)),
+	),
+
+	// Logger service
+	wire.NewSet(
+		logger.New,
+		hooks.NewHookCollection,
+		wire.Struct(new(hooks.LogrusReporter), "*"),
+		wire.Struct(new(logger.LogrusFormatter), "*"),
+		wire.Bind(new(logger.Logger), new(*logger.LogrusLogger)),
+	),
+
+	// Reporter service
+	wire.NewSet(
+		reporter.New,
+		wire.Bind(new(reporter.Reporter), new(*reporter.LogtailReporter)),
+	),
+)
diff --git a/service/logger/formatter.go b/service/logger/formatter.go
new file mode 100644
index 0000000..bdaa27d
--- /dev/null
+++ b/service/logger/formatter.go
@@ -0,0 +1,28 @@
+package logger
+
+import (
+	"github.com/sirupsen/logrus"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/log"
+)
+
+type LogrusFormatter struct {
+	Util log.LogUtils
+}
+
+var colorMap = map[logrus.Level]log.Color{
+	logrus.TraceLevel: log.ForeWhite,
+	logrus.DebugLevel: log.ForeWhite,
+	logrus.InfoLevel:  log.ForeGreen,
+	logrus.WarnLevel:  log.ForeYellow,
+	logrus.ErrorLevel: log.ForeRed,
+	logrus.PanicLevel: log.ForeRed,
+}
+
+func (l *LogrusFormatter) Format(entry *logrus.Entry) ([]byte, error) {
+	return []byte(l.Util.FormattedOutput(
+		entry.Message,
+		"App",
+		entry.Level.String(),
+		colorMap[entry.Level],
+	)), nil
+}
diff --git a/service/logger/hooks/hooks.go b/service/logger/hooks/hooks.go
new file mode 100644
index 0000000..3fe1856
--- /dev/null
+++ b/service/logger/hooks/hooks.go
@@ -0,0 +1,21 @@
+package hooks
+
+import "github.com/sirupsen/logrus"
+
+type LogrusHookCollection []LogrusLogHook
+
+type LogrusLogHook struct {
+	Hook             logrus.Hook
+	IsProductionOnly bool
+}
+
+func NewHookCollection(
+	reporter LogrusReporter,
+) LogrusHookCollection {
+	return []LogrusLogHook{
+		{
+			IsProductionOnly: true,
+			Hook:             reporter,
+		},
+	}
+}
diff --git a/service/logger/hooks/reporter.go b/service/logger/hooks/reporter.go
new file mode 100644
index 0000000..cb08c25
--- /dev/null
+++ b/service/logger/hooks/reporter.go
@@ -0,0 +1,33 @@
+package hooks
+
+import (
+	"time"
+
+	"github.com/sirupsen/logrus"
+	"gitlab.informatika.org/ocw/ocw-backend/service/reporter"
+)
+
+type LogrusReporter struct {
+	Reporter reporter.Reporter
+}
+
+func (LogrusReporter) Levels() []logrus.Level {
+	return []logrus.Level{
+		logrus.PanicLevel,
+		logrus.FatalLevel,
+		logrus.ErrorLevel,
+		logrus.WarnLevel,
+		logrus.InfoLevel,
+	}
+}
+
+func (l LogrusReporter) Fire(entry *logrus.Entry) error {
+	payload := reporter.ReporterPayload{
+		Level:     entry.Level.String(),
+		Timestamp: entry.Time.Format(time.RFC3339),
+		Message:   entry.Message,
+	}
+
+	l.Reporter.Send(payload)
+	return nil
+}
diff --git a/service/logger/logger.go b/service/logger/logger.go
new file mode 100644
index 0000000..96686e9
--- /dev/null
+++ b/service/logger/logger.go
@@ -0,0 +1,50 @@
+package logger
+
+import (
+	"github.com/sirupsen/logrus"
+	"gitlab.informatika.org/ocw/ocw-backend/service/logger/hooks"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/env"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/log"
+)
+
+type LogrusLogger struct {
+	logger *logrus.Logger
+}
+
+// Object Builder
+var createdLogger *LogrusLogger = nil
+
+func New(
+	env *env.Environment,
+	logUtil log.LogUtils,
+	hooks hooks.LogrusHookCollection,
+) *LogrusLogger {
+	if createdLogger != nil {
+		return createdLogger
+	}
+
+	log := logrus.New()
+
+	log.SetFormatter(&LogrusFormatter{
+		Util: logUtil,
+	})
+
+	for _, hook := range hooks {
+		if hook.IsProductionOnly && env.AppEnvironment == "PRODUCTION" ||
+			!hook.IsProductionOnly {
+			log.AddHook(hook.Hook)
+		}
+	}
+
+	if env.AppEnvironment == "PRODUCTION" {
+		log.SetLevel(logrus.InfoLevel)
+	} else {
+		log.SetLevel(logrus.DebugLevel)
+	}
+
+	createdLogger = &LogrusLogger{
+		logger: log,
+	}
+
+	return createdLogger
+}
diff --git a/service/logger/method.go b/service/logger/method.go
new file mode 100644
index 0000000..439b406
--- /dev/null
+++ b/service/logger/method.go
@@ -0,0 +1,17 @@
+package logger
+
+func (l *LogrusLogger) Debug(message string) {
+	l.logger.Debug(message)
+}
+
+func (l *LogrusLogger) Info(message string) {
+	l.logger.Info(message)
+}
+
+func (l *LogrusLogger) Warning(message string) {
+	l.logger.Warn(message)
+}
+
+func (l *LogrusLogger) Error(message string) {
+	l.logger.Error(message)
+}
diff --git a/service/logger/type.go b/service/logger/type.go
new file mode 100644
index 0000000..b53578c
--- /dev/null
+++ b/service/logger/type.go
@@ -0,0 +1,8 @@
+package logger
+
+type Logger interface {
+	Debug(message string)
+	Info(message string)
+	Warning(message string)
+	Error(message string)
+}
diff --git a/service/reporter/logtail.go b/service/reporter/logtail.go
new file mode 100644
index 0000000..f156450
--- /dev/null
+++ b/service/reporter/logtail.go
@@ -0,0 +1,153 @@
+package reporter
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"sync"
+	"time"
+
+	"gitlab.informatika.org/ocw/ocw-backend/utils/env"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/log"
+)
+
+type LogtailReporter struct {
+	env        *env.Environment
+	logUtil    log.LogUtils
+	logQueue   []ReporterPayload
+	mutex      sync.Mutex
+	httpClient *http.Client
+	isStarted  bool
+}
+
+func New(
+	env *env.Environment,
+	logUtil log.LogUtils,
+) *LogtailReporter {
+	return &LogtailReporter{
+		env,
+		logUtil,
+		[]ReporterPayload{},
+		sync.Mutex{},
+		&http.Client{
+			Transport: &http.Transport{
+				IdleConnTimeout: time.Duration(env.HttpReqTimeout) * time.Second,
+			},
+		},
+		false,
+	}
+}
+
+func (l *LogtailReporter) Send(payload ReporterPayload) {
+	if !l.isStarted {
+		return
+	}
+
+	l.mutex.Lock()
+	defer l.mutex.Unlock()
+
+	l.logQueue = append(l.logQueue, payload)
+}
+
+func (l *LogtailReporter) Flush() {
+	l.mutex.Lock()
+	defer l.mutex.Unlock()
+
+	payloadBytes, err := json.Marshal(l.logQueue)
+
+	if err != nil {
+		l.logUtil.PrintFormattedOutput(
+			fmt.Sprintf("Some error happened when parse json: %s", err),
+			"REPORT",
+			"ERROR",
+			log.ForeRed,
+		)
+	} else {
+		l.logQueue = []ReporterPayload{}
+
+		go func() {
+			reader := bytes.NewReader(payloadBytes)
+			req, err := http.NewRequest("POST", "https://in.logtail.com", reader)
+
+			if err != nil {
+				l.logUtil.PrintFormattedOutput(
+					fmt.Sprintf("Some error happened when creating request: %s", err),
+					"REPORT",
+					"ERROR",
+					log.ForeRed,
+				)
+				return
+			}
+
+			req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", l.env.LogtailToken))
+			req.Header.Add("Content-Type", "application/json")
+
+			res, err := l.httpClient.Do(req)
+
+			if err != nil {
+				l.logUtil.PrintFormattedOutput(
+					fmt.Sprintf("Some error happened when sending request: %s", err),
+					"REPORT",
+					"ERROR",
+					log.ForeRed,
+				)
+				return
+			}
+
+			if res.StatusCode != http.StatusOK {
+				l.logUtil.PrintFormattedOutput(
+					fmt.Sprintf("Request respose is not 200 OK: got %d", res.StatusCode),
+					"REPORT",
+					"ERROR",
+					log.ForeRed,
+				)
+			}
+		}()
+	}
+
+}
+
+func (l *LogtailReporter) Start(ctx context.Context) {
+	if l.env.AppEnvironment != "PRODUCTION" {
+		l.logUtil.PrintFormattedOutput(
+			"Reporter is not started due to non-production environment",
+			"REPORT",
+			"WARNING",
+			log.ForeYellow)
+		return
+	}
+
+	go func() {
+		l.isStarted = true
+		defer func() { l.isStarted = false }()
+		defer l.Flush()
+
+		timer := time.NewTicker(time.Second)
+		defer timer.Stop()
+
+		l.logUtil.PrintFormattedOutput(
+			"Reporter started to listen...",
+			"REPORT",
+			"INFO",
+			log.ForeBlue,
+		)
+
+		for {
+			select {
+			case <-ctx.Done():
+				break
+			case <-timer.C:
+				l.Flush()
+			}
+		}
+	}()
+}
+
+func (l *LogtailReporter) Clear() {
+	l.mutex.Lock()
+	defer l.mutex.Unlock()
+
+	l.logQueue = []ReporterPayload{}
+}
diff --git a/service/reporter/type.go b/service/reporter/type.go
new file mode 100644
index 0000000..a910ed0
--- /dev/null
+++ b/service/reporter/type.go
@@ -0,0 +1,16 @@
+package reporter
+
+import "context"
+
+type ReporterPayload struct {
+	Timestamp string `json:"dt"`
+	Level     string `json:"level"`
+	Message   string `json:"message"`
+}
+
+type Reporter interface {
+	Send(payload ReporterPayload)
+	Flush()
+	Start(ctx context.Context)
+	Clear()
+}
diff --git a/utils/app/app.go b/utils/app/app.go
new file mode 100644
index 0000000..dbde6d4
--- /dev/null
+++ b/utils/app/app.go
@@ -0,0 +1,50 @@
+package app
+
+import (
+	"github.com/go-chi/chi/v5"
+	"gitlab.informatika.org/ocw/ocw-backend/middleware"
+	"gitlab.informatika.org/ocw/ocw-backend/routes"
+	"gitlab.informatika.org/ocw/ocw-backend/service/logger"
+	"gitlab.informatika.org/ocw/ocw-backend/service/reporter"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/env"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/log"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/res"
+)
+
+type HttpServer struct {
+	server   *chi.Mux
+	log      logger.Logger
+	logUtil  log.LogUtils
+	res      res.Resource
+	env      *env.Environment
+	reporter reporter.Reporter
+}
+
+func New(
+	middlewares middleware.MiddlewareCollection,
+	routes routes.RouteCollection,
+	env *env.Environment,
+	log logger.Logger,
+	logUtil log.LogUtils,
+	res res.Resource,
+	reporter reporter.Reporter,
+) *HttpServer {
+	r := chi.NewRouter()
+
+	for _, handler := range middlewares.Register() {
+		r.Use(handler.Handle)
+	}
+
+	for _, group := range routes.Register() {
+		r.Group(group.Register)
+	}
+
+	return &HttpServer{
+		server:   r,
+		log:      log,
+		res:      res,
+		logUtil:  logUtil,
+		env:      env,
+		reporter: reporter,
+	}
+}
diff --git a/utils/app/list.go b/utils/app/list.go
new file mode 100644
index 0000000..481a1ad
--- /dev/null
+++ b/utils/app/list.go
@@ -0,0 +1,71 @@
+package app
+
+import (
+	"fmt"
+
+	"gitlab.informatika.org/ocw/ocw-backend/utils/log"
+)
+
+var colorMap = map[string][]log.Color{
+	"GET":    {log.BackGreen, log.ForeWhite},
+	"POST":   {log.BackBlue, log.ForeWhite},
+	"PUT":    {log.BackCyan, log.ForeWhite},
+	"PATCH":  {log.BackMagenta, log.ForeWhite},
+	"DELETE": {log.BackRed, log.ForeWhite},
+}
+
+func colorizeMethod(name string) string {
+	res := ""
+	val, ok := colorMap[name]
+
+	if ok {
+		res = string(val[0]) + string(val[1])
+	} else {
+		res = string(log.ForeBlack) + string(log.BackWhite)
+	}
+
+	res = res + " "
+
+	res = res + name
+
+	for i := 0; i < 8-(len(name)+1); i++ {
+		res = res + " "
+	}
+
+	return res + string(log.Reset)
+}
+
+func (l HttpServer) ListRoute() {
+	routeData := map[string][]string{}
+
+	for _, route := range l.server.Routes() {
+		for method := range route.Handlers {
+			name := route.Pattern
+
+			if routeData[method] == nil {
+				routeData[method] = []string{name}
+			} else {
+				routeData[method] = append(routeData[method], name)
+			}
+		}
+	}
+
+	l.log.Info("Routes Information:")
+	l.log.Info("")
+
+	loggedMethod := []string{
+		"GET", "POST", "PUT", "PATCH", "DELETE",
+	}
+
+	for _, method := range loggedMethod {
+		for _, pattern := range routeData[method] {
+			l.log.Info(
+				fmt.Sprintf("%s %s",
+					colorizeMethod(method),
+					pattern),
+			)
+		}
+	}
+
+	l.log.Info("")
+}
diff --git a/utils/app/start.go b/utils/app/start.go
new file mode 100644
index 0000000..1ea8ecd
--- /dev/null
+++ b/utils/app/start.go
@@ -0,0 +1,78 @@
+package app
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"os"
+	"os/signal"
+	"sync"
+	"syscall"
+	"time"
+
+	"gitlab.informatika.org/ocw/ocw-backend/utils/log"
+)
+
+func (l *HttpServer) Start() {
+	listenAddr := fmt.Sprintf("%s:%d", l.env.ListenAddress, l.env.ListenPort)
+
+	server := &http.Server{
+		Addr:    listenAddr,
+		Handler: l.server,
+	}
+
+	serverCtx, cancelServer := context.WithCancel(context.Background())
+	l.reporter.Start(serverCtx)
+
+	sig := make(chan os.Signal, 3)
+	signal.Notify(sig, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
+
+	go func() {
+		defer cancelServer()
+		defer l.log.Info("🛑 Server is successfully shut down")
+
+		<-sig
+
+		forceQuit, cancelForceQuit := context.WithTimeout(context.Background(), 30*time.Second)
+		defer cancelForceQuit()
+
+		group := sync.WaitGroup{}
+		group.Add(1)
+
+		go func() {
+			defer group.Done()
+
+			l.log.Info("⏱️ Gracefully shutdown....")
+			<-forceQuit.Done()
+
+			if forceQuit.Err() == context.DeadlineExceeded {
+				l.log.Error("⏱️ Waiting timeout, force shutdown...")
+			}
+		}()
+
+		err := server.Shutdown(forceQuit)
+		if err != nil {
+			l.log.Error(err.Error())
+		}
+
+		cancelForceQuit()
+		group.Wait()
+	}()
+
+	l.log.Info(fmt.Sprintf("🌎 Server Listen at %s",
+		l.logUtil.ColoredOutput(
+			"http://"+listenAddr,
+			log.ForeGreen,
+		),
+	))
+
+	err := server.ListenAndServe()
+
+	if err != nil && err != http.ErrServerClosed {
+		l.log.Error("🔥 Failed to start server")
+		l.log.Error(err.Error())
+		os.Exit(1)
+	}
+
+	<-serverCtx.Done()
+}
diff --git a/utils/app/type.go b/utils/app/type.go
new file mode 100644
index 0000000..92bc9d5
--- /dev/null
+++ b/utils/app/type.go
@@ -0,0 +1,7 @@
+package app
+
+type Server interface {
+	Start()
+	ListRoute()
+	Version()
+}
diff --git a/utils/app/version.go b/utils/app/version.go
new file mode 100644
index 0000000..6b3cac8
--- /dev/null
+++ b/utils/app/version.go
@@ -0,0 +1,33 @@
+package app
+
+import (
+	"fmt"
+
+	"gitlab.informatika.org/ocw/ocw-backend/utils/log"
+)
+
+func (l HttpServer) Version() {
+	data, err := l.res.GetStringResource("ascii.art")
+
+	if err == nil {
+		fmt.Println(
+			l.logUtil.ColoredOutput(
+				data,
+				log.ForeGreen,
+			),
+		)
+		println()
+	}
+
+	data, err = l.res.GetStringResource("version")
+
+	if err == nil {
+		fmt.Println(
+			l.logUtil.ColoredOutput(
+				data,
+				log.ForeCyan,
+			),
+		)
+		println()
+	}
+}
diff --git a/utils/di.go b/utils/di.go
new file mode 100644
index 0000000..ac8adc5
--- /dev/null
+++ b/utils/di.go
@@ -0,0 +1,36 @@
+package utils
+
+import (
+	"github.com/google/wire"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/app"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/env"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/httputil"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/log"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/res"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/wrapper"
+)
+
+var UtilSet = wire.NewSet(
+	// env
+	env.New,
+
+	// httputil utility
+	wire.Struct(new(httputil.HttpUtilImpl), "*"),
+	wire.Bind(new(httputil.HttpUtil), new(*httputil.HttpUtilImpl)),
+
+	// log utility
+	wire.Struct(new(log.LogUtilsImpl), "*"),
+	wire.Bind(new(log.LogUtils), new(*log.LogUtilsImpl)),
+
+	// res utility
+	wire.Struct(new(res.EmbedResources), "*"),
+	wire.Bind(new(res.Resource), new(*res.EmbedResources)),
+
+	// wrapper utility
+	wire.Struct(new(wrapper.WrapperUtilImpl), "*"),
+	wire.Bind(new(wrapper.WrapperUtil), new(*wrapper.WrapperUtilImpl)),
+
+	// app
+	app.New,
+	wire.Bind(new(app.Server), new(*app.HttpServer)),
+)
diff --git a/utils/env/env.go b/utils/env/env.go
new file mode 100644
index 0000000..a249e0f
--- /dev/null
+++ b/utils/env/env.go
@@ -0,0 +1,46 @@
+package env
+
+import (
+	"os"
+
+	"github.com/caarlos0/env/v6"
+	"github.com/joho/godotenv"
+)
+
+type Environment struct {
+	AppEnvironment string `env:"ENV"`
+	ListenAddress  string `env:"LISTEN_ADDR" envDefault:"0.0.0.0"`
+
+	ListenPort   int    `env:"PORT" envDefault:"8080"`
+	LogtailToken string `env:"LOGTAIL_TOKEN"`
+
+	HttpReqTimeout int64 `env:"HTTP_SEC_TIMEOUT" envDefault:"1"`
+}
+
+func New() (*Environment, error) {
+	if os.Getenv("ENV") == "PRODUCTION" {
+		return NewEnv()
+	}
+
+	return NewDotEnv()
+}
+
+func NewEnv() (*Environment, error) {
+	cfg := &Environment{}
+
+	if err := env.Parse(cfg); err != nil {
+		return nil, err
+	}
+
+	return cfg, nil
+}
+
+func NewDotEnv() (*Environment, error) {
+	err := godotenv.Load()
+
+	if err != nil {
+		return nil, err
+	}
+
+	return NewEnv()
+}
diff --git a/utils/httputil/impl.go b/utils/httputil/impl.go
new file mode 100644
index 0000000..41cc17f
--- /dev/null
+++ b/utils/httputil/impl.go
@@ -0,0 +1,3 @@
+package httputil
+
+type HttpUtilImpl struct{}
diff --git a/utils/httputil/parse.go b/utils/httputil/parse.go
new file mode 100644
index 0000000..d438e56
--- /dev/null
+++ b/utils/httputil/parse.go
@@ -0,0 +1,11 @@
+package httputil
+
+import (
+	"encoding/json"
+	"net/http"
+)
+
+func (HttpUtilImpl) ParseJson(r *http.Request, output interface{}) error {
+	decoder := json.NewDecoder(r.Body)
+	return decoder.Decode(output)
+}
diff --git a/utils/httputil/type.go b/utils/httputil/type.go
new file mode 100644
index 0000000..5eec02c
--- /dev/null
+++ b/utils/httputil/type.go
@@ -0,0 +1,9 @@
+package httputil
+
+import "net/http"
+
+type HttpUtil interface {
+	WriteSuccessJson(w http.ResponseWriter, payload interface{}) error
+	WriteJson(w http.ResponseWriter, httpCode int, payload interface{}) error
+	ParseJson(r *http.Request, output interface{}) error
+}
diff --git a/utils/httputil/write.go b/utils/httputil/write.go
new file mode 100644
index 0000000..effce03
--- /dev/null
+++ b/utils/httputil/write.go
@@ -0,0 +1,16 @@
+package httputil
+
+import (
+	"encoding/json"
+	"net/http"
+)
+
+func (HttpUtilImpl) WriteJson(w http.ResponseWriter, httpCode int, payload interface{}) error {
+	encoder := json.NewEncoder(w)
+	w.WriteHeader(httpCode)
+	return encoder.Encode(payload)
+}
+
+func (h HttpUtilImpl) WriteSuccessJson(w http.ResponseWriter, payload interface{}) error {
+	return h.WriteJson(w, http.StatusOK, payload)
+}
diff --git a/utils/log/color.go b/utils/log/color.go
new file mode 100644
index 0000000..c339153
--- /dev/null
+++ b/utils/log/color.go
@@ -0,0 +1,27 @@
+package log
+
+type Color string
+
+const Reset Color = "\u001b[0m"
+
+const (
+	ForeBlack 	Color = "\u001b[30m"
+	ForeRed 		Color = "\u001b[31m"
+	ForeGreen 	Color = "\u001b[32m"
+	ForeYellow 	Color = "\u001b[33m"
+	ForeBlue	 	Color = "\u001b[34m"
+	ForeMagenta	Color = "\u001b[35m"
+	ForeCyan		Color = "\u001b[36m"
+	ForeWhite		Color = "\u001b[37m"
+)
+
+const (
+	BackBlack 	Color = "\u001b[40m"
+	BackRed 		Color = "\u001b[41m"
+	BackGreen 	Color = "\u001b[42m"
+	BackYellow 	Color = "\u001b[43m"
+	BackBlue	 	Color = "\u001b[44m"
+	BackMagenta	Color = "\u001b[45m"
+	BackCyan		Color = "\u001b[46m"
+	BackWhite		Color = "\u001b[47m"
+)
\ No newline at end of file
diff --git a/utils/log/output.go b/utils/log/output.go
new file mode 100644
index 0000000..773bece
--- /dev/null
+++ b/utils/log/output.go
@@ -0,0 +1,31 @@
+package log
+
+import (
+	"fmt"
+	"time"
+)
+
+type LogUtilsImpl struct{}
+
+func (l LogUtilsImpl) FormattedOutput(text string, process string, logType string, color Color) string {
+	return fmt.Sprintf("%s %s: [%s] %s\n",
+		time.Now().Format("2006-01-02 15:04:05 MST"),
+		l.ColoredOutput(logType, color),
+		process,
+		text,
+	)
+}
+
+func (LogUtilsImpl) ColoredOutput(text string, color Color) string {
+	return fmt.Sprintf("%s%s%s",
+		color,
+		text,
+		Reset,
+	)
+}
+
+func (l LogUtilsImpl) PrintFormattedOutput(text string, process string, logType string, color Color) {
+	print(
+		l.FormattedOutput(text, process, logType, color),
+	)
+}
diff --git a/utils/log/type.go b/utils/log/type.go
new file mode 100644
index 0000000..6bd07ca
--- /dev/null
+++ b/utils/log/type.go
@@ -0,0 +1,7 @@
+package log
+
+type LogUtils interface {
+	PrintFormattedOutput(text string, process string, logType string, color Color)
+	FormattedOutput(text string, process string, logType string, color Color) string
+	ColoredOutput(text string, color Color) string
+}
diff --git a/utils/res/data/ascii.art b/utils/res/data/ascii.art
new file mode 100644
index 0000000..269bead
--- /dev/null
+++ b/utils/res/data/ascii.art
@@ -0,0 +1,8 @@
+   ____                    _____                       __          __            
+  / __ \                  / ____|                      \ \        / /            
+ | |  | |_ __   ___ _ __ | |     ___  _   _ _ __ ___  __\ \  /\  / /_ _ _ __ ___ 
+ | |  | | '_ \ / _ \ '_ \| |    / _ \| | | | '__/ __|/ _ \ \/  \/ / _` | '__/ _ \
+ | |__| | |_) |  __/ | | | |___| (_) | |_| | |  \__ \  __/\  /\  / (_| | | |  __/
+  \____/| .__/ \___|_| |_|\_____\___/ \__,_|_|  |___/\___| \/  \/ \__,_|_|  \___|
+        | |                                                                      
+        |_|                                                                      
diff --git a/utils/res/data/version b/utils/res/data/version
new file mode 100644
index 0000000..cfbbe7b
--- /dev/null
+++ b/utils/res/data/version
@@ -0,0 +1 @@
+ALPHA-1.0.0
\ No newline at end of file
diff --git a/utils/res/embed.go b/utils/res/embed.go
new file mode 100644
index 0000000..598ad06
--- /dev/null
+++ b/utils/res/embed.go
@@ -0,0 +1,25 @@
+package res
+
+import (
+	"embed"
+)
+
+//go:embed data/*
+var data embed.FS
+
+type EmbedResources struct{}
+
+func (EmbedResources) GetBytesResource(path string) ([]byte, error) {
+	return data.ReadFile("data/" + path)
+}
+
+
+func (EmbedResources) GetStringResource(path string) (string, error) {
+	content, err := data.ReadFile("data/" + path)
+
+	if err != nil {
+		return "", err
+	}
+
+	return string(content), nil
+}
\ No newline at end of file
diff --git a/utils/res/res.go b/utils/res/res.go
new file mode 100644
index 0000000..387823d
--- /dev/null
+++ b/utils/res/res.go
@@ -0,0 +1,6 @@
+package res
+
+type Resource interface {
+	GetBytesResource(path string) ([]byte, error)
+	GetStringResource(path string) (string, error)
+}
diff --git a/utils/wrapper/type.go b/utils/wrapper/type.go
new file mode 100644
index 0000000..0270714
--- /dev/null
+++ b/utils/wrapper/type.go
@@ -0,0 +1,8 @@
+package wrapper
+
+import "gitlab.informatika.org/ocw/ocw-backend/model/web"
+
+type WrapperUtil interface {
+	SuccessResponseWrap(data interface{}) *web.BaseResponse
+	ErrorResponseWrap(message string, payload interface{}) *web.BaseResponse
+}
diff --git a/utils/wrapper/wrapper.go b/utils/wrapper/wrapper.go
new file mode 100644
index 0000000..d35e605
--- /dev/null
+++ b/utils/wrapper/wrapper.go
@@ -0,0 +1,21 @@
+package wrapper
+
+import "gitlab.informatika.org/ocw/ocw-backend/model/web"
+
+type WrapperUtilImpl struct{}
+
+func (WrapperUtilImpl) SuccessResponseWrap(data interface{}) *web.BaseResponse {
+	return &web.BaseResponse{
+		Status:  web.Success,
+		Message: "success",
+		Data:    data,
+	}
+}
+
+func (WrapperUtilImpl) ErrorResponseWrap(message string, payload interface{}) *web.BaseResponse {
+	return &web.BaseResponse{
+		Status:  web.Failed,
+		Message: message,
+		Data:    payload,
+	}
+}
-- 
GitLab