Package with functions for creating a new token with custom claims, validating a token, and getting claims from a token.
errors.go
contains predefined errors, such as the NotFoundErr and AlreadyExistsErr. They are structs which implement the Error interface.postgres.go
contains code for establishing a new database connection pool.user.go
andpost.go
contain basic CRUD operations for users and posts, respectively.
helpers.go
: basic helper methods, such as getUserFromContext and validatePageAndLimit.middleware.go
: basic authentication, CORS and error handling middleware (explained below).responses.go
: frequently used responses, such as successResponse (200 OK + message), badRequestResponse (400 Bad Request + message) etc...server.go
: code for instantiating a new server. All endpoints are defined there, along with the middleware which the server will be using.user.go
: user related handlers, such as handlers for registering an account and logging in.
Very basic package for validating user input. At the moment, it only has a method that checks if a string has been provided, but can be easily extended to do more complex validation.
There are two types of groups, "public" and "auth". In case a collection has both public and protected routes (such as users
, the register and login endpoints are public,
while the endpoint for fetching personal posts is protected), we create two types of groups. In this example, we have created usersPublic
and usersAuth
groups:
v1 := router.Group("/v1")
usersPublic := v1.Group("/users")
{
usersPublic.POST("/register", s.registerUserHandler)
usersPublic.POST("/login", s.loginUserHandler)
}
usersAuth := v1.Group("/users")
usersAuth.Use(s.userAuth)
{
usersAuth.GET("/posts", s.getPostsHandler)
}
Errors which occur frequently are handled in the errorHandler middleware. These errors are in most cases database errors.
This is done in order to reduce boilerplate error handling in handlers. Example:
Let' say we are calling the Create() method in the registerUserHandler:
// server/users.go
createdUser, err := s.UserRepository.Create(user)
if err != nil {
c.Error(fmt.Errorf("registerUserHandler: %w", err))
return
}
We can call c.Error() and pass the error (in this case we wrapped it for tracing purposes), the errorHandler middleware will then proceed to handle the db error:
func (s *Server) errorHandler() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
err := c.Errors.Last()
if err != nil {
var alreadyExistsErr *repository.AlreadyExistsErr
var notFoundErr *repository.NotFoundErr
switch {
case errors.As(err, &alreadyExistsErr):
s.newError(c, http.StatusConflict, alreadyExistsErr)
case errors.As(err, ¬FoundErr):
s.newError(c, http.StatusNotFound, notFoundErr)
default:
s.internalServerErrorResponse(c, err)
}
}
}
}