Skip to content

Commit 952b4dd

Browse files
feat: Add health endpoint (#4)
* Add health endpoint Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org> * Update Makefile --------- Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>
1 parent 9aa42b1 commit 952b4dd

File tree

10 files changed

+157
-13
lines changed

10 files changed

+157
-13
lines changed

api/server.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,21 @@ import (
99
)
1010

1111
// New creates a new instance of the API server
12-
func New(serverAddress string, flagHandlers handler.Flags) *Server {
12+
func New(serverAddress string, flagHandlers handler.Flags, healthHandlers handler.Health) *Server {
1313
return &Server{
14-
flagHandlers: flagHandlers,
15-
apiEcho: echo.New(),
16-
serverAddress: serverAddress,
14+
flagHandlers: flagHandlers,
15+
healthHandlers: healthHandlers,
16+
apiEcho: echo.New(),
17+
serverAddress: serverAddress,
1718
}
1819
}
1920

2021
// Server is the struct that represents the API server
2122
type Server struct {
22-
flagHandlers handler.Flags
23-
apiEcho *echo.Echo
24-
serverAddress string
23+
flagHandlers handler.Flags
24+
healthHandlers handler.Health
25+
apiEcho *echo.Echo
26+
serverAddress string
2527
}
2628

2729
// Start starts the API server
@@ -33,6 +35,9 @@ func (s *Server) Start() {
3335
// Middlewares
3436
s.apiEcho.Use(middleware.CORSWithConfig(middleware.DefaultCORSConfig))
3537

38+
// init health routes
39+
s.apiEcho.POST("/health", s.healthHandlers.Health)
40+
3641
// init API routes
3742
groupV1 := s.apiEcho.Group("/v1")
3843
groupV1.GET("/flags", s.flagHandlers.GetAllFeatureFlags)

dao/flags.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,7 @@ type Flags interface {
2424

2525
// DeleteFlagByID delete a flag
2626
DeleteFlagByID(ctx context.Context, id string) error
27+
28+
// Ping check that the data layer is available
29+
Ping() error
2730
}

dao/postgres_impl.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ func NewPostgresDao(serverHost string, port int, database string, username strin
2727
instance := &pgFlagImpl{
2828
conn: conn,
2929
}
30-
3130
return instance, nil
3231
}
3332

@@ -383,3 +382,10 @@ func (m *pgFlagImpl) updateRule(
383382

384383
return errTx
385384
}
385+
386+
func (m *pgFlagImpl) Ping() error {
387+
if m.conn == nil {
388+
return errors.New("database connection is nil")
389+
}
390+
return m.conn.Ping()
391+
}

docs/docs.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,29 @@ const docTemplate = `{
2626
"host": "{{.Host}}",
2727
"basePath": "{{.BasePath}}",
2828
"paths": {
29+
"/health": {
30+
"post": {
31+
"description": "Check if the API is up and running and that the database is available.",
32+
"tags": [
33+
"Feature Monitoring"
34+
],
35+
"summary": "Health endpoint of the API",
36+
"responses": {
37+
"200": {
38+
"description": "Created",
39+
"schema": {
40+
"$ref": "#/definitions/handler.successResponse"
41+
}
42+
},
43+
"500": {
44+
"description": "Internal server error",
45+
"schema": {
46+
"$ref": "#/definitions/model.HTTPError"
47+
}
48+
}
49+
}
50+
}
51+
},
2952
"/v1/flags": {
3053
"get": {
3154
"description": "GET request to get all the flags available.",
@@ -282,6 +305,17 @@ const docTemplate = `{
282305
}
283306
},
284307
"definitions": {
308+
"handler.successResponse": {
309+
"type": "object",
310+
"properties": {
311+
"code": {
312+
"type": "integer"
313+
},
314+
"message": {
315+
"type": "string"
316+
}
317+
}
318+
},
285319
"model.FeatureFlag": {
286320
"type": "object",
287321
"properties": {

docs/swagger.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,29 @@
1818
},
1919
"basePath": "/",
2020
"paths": {
21+
"/health": {
22+
"post": {
23+
"description": "Check if the API is up and running and that the database is available.",
24+
"tags": [
25+
"Feature Monitoring"
26+
],
27+
"summary": "Health endpoint of the API",
28+
"responses": {
29+
"200": {
30+
"description": "Created",
31+
"schema": {
32+
"$ref": "#/definitions/handler.successResponse"
33+
}
34+
},
35+
"500": {
36+
"description": "Internal server error",
37+
"schema": {
38+
"$ref": "#/definitions/model.HTTPError"
39+
}
40+
}
41+
}
42+
}
43+
},
2144
"/v1/flags": {
2245
"get": {
2346
"description": "GET request to get all the flags available.",
@@ -274,6 +297,17 @@
274297
}
275298
},
276299
"definitions": {
300+
"handler.successResponse": {
301+
"type": "object",
302+
"properties": {
303+
"code": {
304+
"type": "integer"
305+
},
306+
"message": {
307+
"type": "string"
308+
}
309+
}
310+
},
277311
"model.FeatureFlag": {
278312
"type": "object",
279313
"properties": {

docs/swagger.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
basePath: /
22
definitions:
3+
handler.successResponse:
4+
properties:
5+
code:
6+
type: integer
7+
message:
8+
type: string
9+
type: object
310
model.FeatureFlag:
411
properties:
512
LastModifiedBy:
@@ -165,6 +172,21 @@ info:
165172
x-logo:
166173
url: https://raw.githubusercontent.com/thomaspoignant/go-feature-flag/main/logo_128.png
167174
paths:
175+
/health:
176+
post:
177+
description: Check if the API is up and running and that the database is available.
178+
responses:
179+
"200":
180+
description: Created
181+
schema:
182+
$ref: '#/definitions/handler.successResponse'
183+
"500":
184+
description: Internal server error
185+
schema:
186+
$ref: '#/definitions/model.HTTPError'
187+
summary: Health endpoint of the API
188+
tags:
189+
- Feature Monitoring
168190
/v1/flags:
169191
get:
170192
description: GET request to get all the flags available.

go.mod

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
module github.com/go-feature-flag/app-api
22

3-
go 1.22.5
4-
5-
toolchain go1.22.7
3+
go 1.22.7
64

75
require (
86
github.com/google/uuid v1.6.0

handler/health.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package handler
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/go-feature-flag/app-api/dao"
7+
"github.com/go-feature-flag/app-api/model"
8+
"github.com/labstack/echo/v4"
9+
)
10+
11+
type Health struct {
12+
dao dao.Flags
13+
}
14+
15+
// NewHealth creates a new instance of the Health handlers
16+
func NewHealth(dao dao.Flags) Health {
17+
return Health{dao: dao}
18+
}
19+
20+
type successResponse struct {
21+
Message string `json:"message"`
22+
Code int `json:"code"`
23+
}
24+
25+
// Health is the health endpoint of the API
26+
// @Summary Health endpoint of the API
27+
// @Tags Feature Monitoring
28+
// @Description Check if the API is up and running and that the database is available.
29+
// @Success 200 {object} successResponse "Created"
30+
// @Failure 500 {object} model.HTTPError "Internal server error"
31+
// @Router /health [post]
32+
func (f Health) Health(c echo.Context) error {
33+
err := f.dao.Ping()
34+
if err != nil {
35+
return c.JSON(model.NewHTTPError(http.StatusInternalServerError, err))
36+
}
37+
return c.JSON(http.StatusOK, successResponse{
38+
Message: "API is up and running",
39+
Code: http.StatusOK,
40+
})
41+
}

main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ func main() {
2727
panic(err)
2828
}
2929
flagHandlers := handler.NewFlags(data)
30+
healthHandlers := handler.NewHealth(data)
3031

31-
apiServer := api.New(":3001", flagHandlers)
32+
apiServer := api.New(":3001", flagHandlers, healthHandlers)
3233
apiServer.Start()
3334
defer func() { apiServer.Stop() }()
3435
}

model/http_error.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ func NewHTTPError(code int, err error) (int, HTTPError) {
55
}
66

77
type HTTPError struct {
8-
ErrorDetails string `json:"errorDetails" example:"An error occurred"`
8+
ErrorDetails string `json:"errorDetails,omitempty" example:"An error occurred"`
99
Code int `json:"code" example:"500"`
1010
}

0 commit comments

Comments
 (0)