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
119122go 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
124127Change the following hosts in the ** .env** and ** .test.env**
125128- DB_HOST=localhost
@@ -156,117 +159,50 @@ Information about the framework
156159package model
157160
158161import (
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
171167type 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
231180package dto
232181
233182import (
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
241190type 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
247196func EmptyInfoSample () *InfoSample {
248- return &InfoSample{}
197+ return &InfoSample{}
249198}
250199
251200func (d *InfoSample ) GetValue () *InfoSample {
252- return d
201+ return d
253202}
254203
255204func (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 {
287223package sample
288224
289225import (
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
299236type Service interface {
300- FindSample (id primitive. ObjectID ) (*model.Sample , error )
237+ FindSample (id uuid. UUID ) (*model.Sample , error )
301238}
302239
303240type 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 {
345302package sample
346303
347304import (
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
356313type controller struct {
357- network.BaseController
358- common.ContextPayload
359- service Service
314+ network.BaseController
315+ common.ContextPayload
316+ service Service
360317}
361318
362319func 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
374331func (c *controller ) MountRoutes (group *gin .RouterGroup ) {
375- group.GET (" /id/:id" , c.getSampleHandler )
332+ group.GET (" /id/:id" , c.getSampleHandler )
376333}
377334
378335func (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