Golang Rest API for NodeJS developer - Part 2
November 18, 2019 • 2 minutes to read
Golang Rest API for NodeJS developer - Part 2
In part 1 we did set up the foundation of our REST API. In this part, we will set up the Register logic for our authentication portion of the app. Authentication is a big part of almost every app we need to build as a developer. One thing it's because is so common you can almost translate the knowledge earn in other languages. In our case for this tutorial, we will use a simple JWT authentication with an email/password combination. Later I maybe plan to add Google OAuth.
The first thing to do is to create the User
struct. Pretty standard stuff. An id who will be auto-increment by PostgreSQL. Some timestamps so we know when the user did get created or updated.
You can also see the JSON tag. If you look at the password
I use -
, this means we do not want the password to get return to the JSON client.
1// domain/users.go23package domain45import "time"67type User struct {8 ID int64 `json:"id"`9 Username string `json:"username"`10 Email string `json:"email"`11 Password string `json:"-"`1213 CreatedAt time.Time `json:"createdAt"`14 UpdatedAt time.Time `json:"updatedAt"`15}
After this, we will create 3 instances of error. This will make our life easier in the long run of the app.
1// domain/errors.go23package domain45import "errors"67var (8 ErrNoResult = errors.New("no result")9 ErrUserWithEmailAlreadyExist = errors.New("user with email already exist")10 ErrUserWithUsernameAlreadyExist = errors.New("user with username already exist")11)
One thing I like when I work with Go is creating the interface need to make the app work before even starting writing the logic. The interface
will be like a contract and make sure my code will follow this. Time to jump on the UserRepo
interface who will be our layer to the database
for the user stuff. I also create a Domain struct who will keep the DB instance. So we can make sure we have only one instance of this last one.
This will also make life easier and no cycle dependencies issue.
1// domain/domain.go2package domain34type UserRepo interface {5 GetByEmail(email string) (*User, error)6 GetByUsername(username string) (*User, error)7 Create(user *User) (*User, error)8}910type DB struct {11 UserRepo UserRepo12}1314type Domain struct {15 DB DB16}
With that, we can start creating the auth domain logic. First, we create a payload struct who will capture the client data request. After this, the Register method will do the logic for creating a user to our app. This one will also handle the error if a user exists for both the email or username, we want those to be unique. Finally, we create a method setPassword that will be filled in later part.
1// domain/auth.go2package domain34type RegisterPayload struct {5 Email string `json:"email"`6 Password string `json:"password"`7 ConfirmPassword string `json:"confirmPassword"`8 Username string `json:"username"`9}1011func (d *Domain) Register(payload RegisterPayload) (*User, error) {12 userExist, _ := d.DB.UserRepo.GetByEmail(payload.Email)13 if userExist != nil {14 return nil, ErrUserWithEmailAlreadyExist15 }1617 userExist, _ = d.DB.UserRepo.GetByUsername(payload.Username)18 if userExist != nil {19 return nil, ErrUserWithUsernameAlreadyExist20 }2122 password, err := d.setPassword(payload.Password)23 if err != nil {24 return nil, err25 }2627 data := &User{28 Username: payload.Username,29 Email: payload.Email,30 Password: *password,31 }3233 user, err := d.DB.UserRepo.Create(data)34 if err != nil {35 return nil, err36 }3738 return user, nil39}4041func (d *Domain) setPassword(password string) (*string, error) {42 return nil, nil43}
After this, we can add the domain to your server struct. This will make the domain available to this one inside the handlers.
1// handlers/handlers.go23package handlers45import (6 "time"78 "github.com/go-chi/chi"9 "github.com/go-chi/chi/middleware"1011 "todo/domain"12)1314type Server struct {15 domain *domain.Domain16}1718func setupMiddleware(r *chi.Mux) {19 r.Use(middleware.RequestID)20 r.Use(middleware.RealIP)21 r.Use(middleware.Compress(6, "application/json"))22 r.Use(middleware.Logger)23 r.Use(middleware.Recoverer)24 r.Use(middleware.URLFormat)25 r.Use(middleware.Timeout(60 * time.Second))26}2728func NewServer(domain *domain.Domain) *Server {29 return &Server{domain: domain}30}3132func SetupRouter(domain *domain.Domain) *chi.Mux {33 server := NewServer(domain)3435 r := chi.NewRouter()3637 setupMiddleware(r)3839 server.setupEndpoints(r)4041 return r42}
Now we can create our users
handlers. Think of it as a controller. I like a thin controller in other frameworks like Laravel
so here I follow
the same idea.
1// handlers/users.go23package handlers45import "net/http"67func (s *Server) registerUser() http.HandlerFunc {8 return func(w http.ResponseWriter, r *http.Request) {9 user, err := s.domain.Register()10 }11}
We can jump after this to our data layer. Remember in the intro I did say we will use Postgres. So we will add a UserRepo
to this Postgres package.
This one will follow the UserRepo
interface from our domain. This will be standard ORM stuff.
1// postgres/user.go23package postgres45import (6 "errors"78 "github.com/go-pg/pg/v9"910 "todo/domain"11)1213type UserRepo struct {14 DB *pg.DB15}1617func (u *UserRepo) GetByEmail(email string) (*domain.User, error) {18 user := new(domain.User)1920 err := u.DB.Model(user).Where("email = ?", email).First()21 if err != nil {22 if errors.Is(err, pg.ErrNoRows) {23 return nil, domain.ErrNoResult24 }2526 return nil, err27 }2829 return user, nil30}3132func (u *UserRepo) GetByUsername(username string) (*domain.User, error) {33 user := new(domain.User)3435 err := u.DB.Model(user).Where("username = ?", username).First()36 if err != nil {37 if errors.Is(err, pg.ErrNoRows) {38 return nil, domain.ErrNoResult39 }4041 return nil, err42 }4344 return user, nil45}4647func (u *UserRepo) Create(user *domain.User) (*domain.User, error) {48 _, err := u.DB.Model(user).Returning("*").Insert()49 if err != nil {50 return nil, err51 }5253 return user, nil54}5556func NewUserRepo(DB *pg.DB) *UserRepo {57 return &UserRepo{DB: DB}58}
Time to update our main.go
with the latest change needed.
1// main.go23package main45import (6 "fmt"7 "log"8 "net/http"9 "os"1011 "github.com/go-pg/pg/v9"1213 "todo/domain"14 "todo/handlers"15 "todo/postgres"16)1718func main() {19 DB := postgres.New(&pg.Options{20 User: "postgres",21 Password: "postgres",22 Database: "todo_dev",23 })2425 defer DB.Close()2627 domainDB := domain.DB{28 UserRepo: postgres.NewUserRepo(DB),29 }3031 d := &domain.Domain{DB: domainDB}3233 r := handlers.SetupRouter(d)3435 port := os.Getenv("PORT")36 if port == "" {37 port = "8081"38 }3940 err := http.ListenAndServe(fmt.Sprintf(":%s", port), r)41 if err != nil {42 log.Fatalf("cannot start server %v", err)43 }44}
Conclusion
If you did like this tutorial don't forget to subscribe to my newsletter below. Also, the video link is at the top of the post. If you have any question don't hesitate to ask in the comment section below.
Happy Coding :)