Skip to content

Commit f3742e7

Browse files
committed
update readme
1 parent a874b13 commit f3742e7

1 file changed

Lines changed: 108 additions & 166 deletions

File tree

README.md

Lines changed: 108 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99

1010
## Create A Blog Service
1111

12-
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.
12+
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. It is built using the `goserve` framework, which offers essential functionalities such as authentication, authorization, database connectivity, and caching.
1313

14-
## Framework
14+
## Framework Libs
1515
- Go
16+
- goserve v2
1617
- Gin
1718
- jwt
1819
- postgres
@@ -23,6 +24,8 @@ This project is a fully production-ready solution designed to implement best pra
2324
- Crypto
2425

2526
**Highlights**
27+
- REST API design
28+
- goserve framework usage
2629
- API key support
2730
- Token based Authentication
2831
- Role based Authorization
@@ -119,7 +122,7 @@ If having any issue
119122
go mod tidy
120123
```
121124

122-
Keep the docker container for `mongo` and `redis` running and **stop** the `goserve` docker container
125+
Keep the docker container for `postgres` and `redis` running and **stop** the `goserve_example_api_server_postgres` docker container
123126

124127
Change the following hosts in the **.env** and **.test.env**
125128
- DB_HOST=localhost
@@ -156,117 +159,50 @@ Information about the framework
156159
package model
157160

158161
import (
159-
"context"
160-
"time"
161-
162-
"github.com/go-playground/validator/v10"
163-
"github.com/afteracademy/goserve/v2/mongo"
164-
"go.mongodb.org/mongo-driver/bson"
165-
"go.mongodb.org/mongo-driver/bson/primitive"
166-
mongod "go.mongodb.org/mongo-driver/mongo"
167-
)
162+
"time"
168163

169-
const CollectionName = "samples"
164+
"github.com/google/uuid"
165+
)
170166

171167
type Sample struct {
172-
ID primitive.ObjectID `bson:"_id,omitempty" validate:"-"`
173-
Field string `bson:"field" validate:"required"`
174-
Status bool `bson:"status" validate:"required"`
175-
CreatedAt time.Time `bson:"createdAt" validate:"required"`
176-
UpdatedAt time.Time `bson:"updatedAt" validate:"required"`
177-
}
178-
179-
func NewSample(field string) (*Sample, error) {
180-
time := time.Now()
181-
doc := Sample{
182-
Field: field,
183-
Status: true,
184-
CreatedAt: time,
185-
UpdatedAt: time,
186-
}
187-
if err := doc.Validate(); err != nil {
188-
return nil, err
189-
}
190-
return &doc, nil
191-
}
192-
193-
func (doc *Sample) GetValue() *Sample {
194-
return doc
195-
}
196-
197-
func (doc *Sample) Validate() error {
198-
validate := validator.New()
199-
return validate.Struct(doc)
200-
}
201-
202-
func (*Sample) EnsureIndexes(db mongo.Database) {
203-
indexes := []mongod.IndexModel{
204-
{
205-
Keys: bson.D{
206-
{Key: "_id", Value: 1},
207-
{Key: "status", Value: 1},
208-
},
209-
},
210-
}
211-
212-
mongo.NewQueryBuilder[Sample](db, CollectionName).Query(context.Background()).CreateIndexes(indexes)
168+
ID uuid.UUID // id
169+
Field string // field
170+
Status bool // status
171+
CreatedAt time.Time // created_at
172+
UpdatedAt time.Time // updated_at
213173
}
214174
```
215175

216-
#### Notes: The Model implements the interface
217-
`github.com/afteracademy/goserve/v2/mongo/database`
218-
219-
```golang
220-
type Document[T any] interface {
221-
EnsureIndexes(Database)
222-
GetValue() *T
223-
Validate() error
224-
}
225-
```
226-
227176
### DTO
228177
`api/sample/dto/create_sample.go`
229178

230179
```go
231180
package dto
232181

233182
import (
234-
"fmt"
235-
"time"
183+
"time"
236184

237-
"github.com/go-playground/validator/v10"
238-
"go.mongodb.org/mongo-driver/bson/primitive"
185+
"github.com/go-playground/validator/v10"
186+
"github.com/google/uuid"
187+
"github.com/afteracademy/goserve/v2/utility"
239188
)
240189

241190
type InfoSample struct {
242-
ID primitive.ObjectID `json:"_id" binding:"required"`
243-
Field string `json:"field" binding:"required"`
244-
CreatedAt time.Time `json:"createdAt" binding:"required"`
191+
ID uuid.UUID `json:"_id" binding:"required"`
192+
Field string `json:"field" binding:"required"`
193+
CreatedAt time.Time `json:"createdAt" binding:"required"`
245194
}
246195

247196
func EmptyInfoSample() *InfoSample {
248-
return &InfoSample{}
197+
return &InfoSample{}
249198
}
250199

251200
func (d *InfoSample) GetValue() *InfoSample {
252-
return d
201+
return d
253202
}
254203

255204
func (d *InfoSample) ValidateErrors(errs validator.ValidationErrors) ([]string, error) {
256-
var msgs []string
257-
for _, err := range errs {
258-
switch err.Tag() {
259-
case "required":
260-
msgs = append(msgs, fmt.Sprintf("%s is required", err.Field()))
261-
case "min":
262-
msgs = append(msgs, fmt.Sprintf("%s must be min %s", err.Field(), err.Param()))
263-
case "max":
264-
msgs = append(msgs, fmt.Sprintf("%s must be max %s", err.Field(), err.Param()))
265-
default:
266-
msgs = append(msgs, fmt.Sprintf("%s is invalid", err.Field()))
267-
}
268-
}
269-
return msgs, nil
205+
return utility.FormatValidationErrors(errs), nil
270206
}
271207
```
272208

@@ -287,42 +223,64 @@ type Dto[T any] interface {
287223
package sample
288224

289225
import (
226+
"context"
227+
290228
"github.com/afteracademy/goserve-example-api-server-postgres/api/sample/dto"
291-
"github.com/afteracademy/goserve-example-api-server-postgres/api/sample/model"
292-
"github.com/afteracademy/goserve/v2/mongo"
293-
"github.com/afteracademy/goserve/v2/network"
294-
"github.com/afteracademy/goserve/v2/redis"
295-
"go.mongodb.org/mongo-driver/bson"
296-
"go.mongodb.org/mongo-driver/bson/primitive"
229+
"github.com/afteracademy/goserve-example-api-server-postgres/api/Sample/model"
230+
"github.com/afteracademy/goserve/v2/network"
231+
"github.com/afteracademy/goserve/v2/redis"
232+
"github.com/jackc/pgx/v5/pgxpool"
233+
"github.com/google/uuid"
297234
)
298235

299236
type Service interface {
300-
FindSample(id primitive.ObjectID) (*model.Sample, error)
237+
FindSample(id uuid.UUID) (*model.Sample, error)
301238
}
302239

303240
type service struct {
304-
network.BaseService
305-
sampleQueryBuilder mongo.QueryBuilder[model.Sample]
306-
infoSampleCache redis.Cache[dto.InfoSample]
241+
network.BaseService
242+
db *pgxpool.Pool
243+
infoSampleCache redis.Cache[dto.InfoSample]
307244
}
308245

309-
func NewService(db mongo.Database, store redis.Store) Service {
310-
return &service{
311-
BaseService: network.NewBaseService(),
312-
sampleQueryBuilder: mongo.NewQueryBuilder[model.Sample](db, model.CollectionName),
313-
infoSampleCache: redis.NewCache[dto.InfoSample](store),
314-
}
246+
func NewService(db *pgxpool.Pool, store redis.Store) Service {
247+
return &service{
248+
BaseService: network.NewBaseService(),
249+
db: db,
250+
infoSampleCache: redis.NewCache[dto.InfoSample](store),
251+
}
315252
}
316253

317-
func (s *service) FindSample(id primitive.ObjectID) (*model.Sample, error) {
318-
filter := bson.M{"_id": id}
319-
320-
msg, err := s.sampleQueryBuilder.SingleQuery().FindOne(filter, nil)
321-
if err != nil {
322-
return nil, err
323-
}
324-
325-
return msg, nil
254+
func (s *service) FindSample(id uuid.UUID) (*model.Sample, error) {
255+
ctx := context.Background()
256+
257+
query := `
258+
SELECT
259+
id,
260+
field,
261+
status,
262+
created_at,
263+
updated_at
264+
FROM samples
265+
WHERE id = $1
266+
`
267+
268+
var m model.Sample
269+
270+
err := s.db.QueryRow(ctx, query, id).
271+
Scan(
272+
&m.ID,
273+
&m.Field,
274+
&m.Status,
275+
&m.CreatedAt,
276+
&m.UpdatedAt,
277+
)
278+
279+
if err != nil {
280+
return nil, err
281+
}
282+
283+
return &m, nil
326284
}
327285
```
328286

@@ -335,7 +293,6 @@ type BaseService interface {
335293
}
336294
```
337295

338-
- Database Query: `mongo.QueryBuilder[model.Sample]` provide the methods to make common mongo queries for the model `model.Sample`
339296
- Redis Cache: `redis.Cache[dto.InfoSample]` provide the methods to make common redis queries for the DTO `dto.InfoSample`
340297

341298
### Controller
@@ -345,56 +302,56 @@ type BaseService interface {
345302
package sample
346303

347304
import (
348-
"github.com/gin-gonic/gin"
349-
"github.com/afteracademy/goserve-example-api-server-postgres/api/sample/dto"
350-
"github.com/afteracademy/goserve-example-api-server-postgres/common"
351-
coredto "github.com/afteracademy/goserve/v2/dto"
352-
"github.com/afteracademy/goserve/v2/network"
353-
"github.com/afteracademy/goserve-example-api-server-postgres/utils"
305+
"github.com/afteracademy/goserve-example-api-server-postgres/api/sample/dto"
306+
"github.com/afteracademy/goserve-example-api-server-postgres/common"
307+
coredto "github.com/afteracademy/goserve/v2/dto"
308+
"github.com/afteracademy/goserve/v2/network"
309+
"github.com/afteracademy/goserve/v2/utility"
310+
"github.com/gin-gonic/gin"
354311
)
355312

356313
type controller struct {
357-
network.BaseController
358-
common.ContextPayload
359-
service Service
314+
network.BaseController
315+
common.ContextPayload
316+
service Service
360317
}
361318

362319
func NewController(
363-
authMFunc network.AuthenticationProvider,
364-
authorizeMFunc network.AuthorizationProvider,
365-
service Service,
320+
authMFunc network.AuthenticationProvider,
321+
authorizeMFunc network.AuthorizationProvider,
322+
service Service,
366323
) network.Controller {
367-
return &controller{
368-
BaseController: network.NewBaseController("/sample", authMFunc, authorizeMFunc),
369-
ContextPayload: common.NewContextPayload(),
370-
service: service,
371-
}
324+
return &controller{
325+
BaseController: network.NewBaseController("/sample", authMFunc, authorizeMFunc),
326+
ContextPayload: common.NewContextPayload(),
327+
service: service,
328+
}
372329
}
373330

374331
func (c *controller) MountRoutes(group *gin.RouterGroup) {
375-
group.GET("/id/:id", c.getSampleHandler)
332+
group.GET("/id/:id", c.getSampleHandler)
376333
}
377334

378335
func (c *controller) getSampleHandler(ctx *gin.Context) {
379-
mongoId, err := network.ReqParams(ctx, coredto.EmptyMongoId())
380-
if err != nil {
381-
c.Send(ctx).BadRequestError(err.Error(), err)
382-
return
383-
}
384-
385-
sample, err := c.service.FindSample(mongoId.ID)
386-
if err != nil {
387-
c.Send(ctx).NotFoundError("sample not found", err)
388-
return
389-
}
390-
391-
data, err := utils.MapTo[dto.InfoSample](sample)
392-
if err != nil {
393-
c.Send(ctx).InternalServerError("something went wrong", err)
394-
return
395-
}
396-
397-
c.Send(ctx).SuccessDataResponse("success", data)
336+
uuidParam, err := network.ReqParams(ctx, coredto.EmptyUUID())
337+
if err != nil {
338+
c.Send(ctx).BadRequestError(err.Error(), err)
339+
return
340+
}
341+
342+
sample, err := c.service.FindSample(uuidParam.ID)
343+
if err != nil {
344+
c.Send(ctx).NotFoundError("sample not found", err)
345+
return
346+
}
347+
348+
data, err := utility.MapTo[dto.InfoSample](sample)
349+
if err != nil {
350+
c.Send(ctx).InternalServerError("something went wrong", err)
351+
return
352+
}
353+
354+
c.Send(ctx).SuccessDataResponse("success", data)
398355
}
399356
```
400357

@@ -450,21 +407,6 @@ func (m *module) Controllers() []network.Controller {
450407
}
451408
```
452409

453-
### Indexing (If Needed)
454-
`startup/indexes.go`
455-
456-
```go
457-
import (
458-
...
459-
sample "github.com/afteracademy/goserve-example-api-server-postgres/api/sample/model"
460-
)
461-
462-
func EnsureDbIndexes(db mongo.Database) {
463-
go mongo.Document[sample.Sample](&sample.Sample{}).EnsureIndexes(db)
464-
...
465-
}
466-
```
467-
468410
## Go Microservices Architecture using goserve
469411
`goserve` also provides `micro` package to build REST API microservices. Find the microservices version of this blog service project at [github.com/afteracademy/gomicro](https://github.com/afteracademy/gomicro)
470412

0 commit comments

Comments
 (0)