loading...

Golang Rest API for NodeJS developer - Part 2

equimper profile image Emanuel Quimper Originally published at equimper.com ・4 min read

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.

// domain/users.go

package domain

import "time"

type User struct {
    ID       int64  `json:"id"`
    Username string `json:"username"`
    Email    string `json:"email"`
    Password string `json:"-"`

    CreatedAt time.Time `json:"createdAt"`
    UpdatedAt time.Time `json:"updatedAt"`
}

After this, we will create 3 instances of error. This will make our life easier in the long run of the app.

// domain/errors.go

package domain

import "errors"

var (
    ErrNoResult = errors.New("no result")
    ErrUserWithEmailAlreadyExist = errors.New("user with email already exist")
    ErrUserWithUsernameAlreadyExist = errors.New("user with username already exist")
)

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.

// domain/domain.go
package domain

type UserRepo interface {
    GetByEmail(email string) (*User, error)
    GetByUsername(username string) (*User, error)
    Create(user *User) (*User, error)
}

type DB struct {
    UserRepo UserRepo
}

type Domain struct {
    DB DB
}

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.

// domain/auth.go
package domain

type RegisterPayload struct {
    Email           string `json:"email"`
    Password        string `json:"password"`
    ConfirmPassword string `json:"confirmPassword"`
    Username        string `json:"username"`
}

func (d *Domain) Register(payload RegisterPayload) (*User, error) {
    userExist, _ := d.DB.UserRepo.GetByEmail(payload.Email)
    if userExist != nil {
        return nil, ErrUserWithEmailAlreadyExist
    }

    userExist, _ = d.DB.UserRepo.GetByUsername(payload.Username)
    if userExist != nil {
        return nil, ErrUserWithUsernameAlreadyExist
    }

    password, err := d.setPassword(payload.Password)
    if err != nil {
        return nil, err
    }

    data := &User{
        Username: payload.Username,
        Email:    payload.Email,
        Password: *password,
    }

    user, err := d.DB.UserRepo.Create(data)
    if err != nil {
        return nil, err
    }

    return user, nil
}

func (d *Domain) setPassword(password string) (*string, error) {
    return nil, nil
}

After this, we can add the domain to your server struct. This will make the domain available to this one inside the handlers.

// handlers/handlers.go

package handlers

import (
    "time"

    "github.com/go-chi/chi"
    "github.com/go-chi/chi/middleware"

    "todo/domain"
)

type Server struct {
    domain *domain.Domain
}

func setupMiddleware(r *chi.Mux) {
    r.Use(middleware.RequestID)
    r.Use(middleware.RealIP)
    r.Use(middleware.Compress(6, "application/json"))
    r.Use(middleware.Logger)
    r.Use(middleware.Recoverer)
    r.Use(middleware.URLFormat)
    r.Use(middleware.Timeout(60 * time.Second))
}

func NewServer(domain *domain.Domain) *Server {
    return &Server{domain: domain}
}

func SetupRouter(domain *domain.Domain) *chi.Mux {
    server := NewServer(domain)

    r := chi.NewRouter()

    setupMiddleware(r)

    server.setupEndpoints(r)

    return r
}

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.

// handlers/users.go

package handlers

import "net/http"

func (s *Server) registerUser() http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        user, err := s.domain.Register()
    }
}

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.

// postgres/user.go

package postgres

import (
    "errors"

    "github.com/go-pg/pg/v9"

    "todo/domain"
)

type UserRepo struct {
    DB *pg.DB
}

func (u *UserRepo) GetByEmail(email string) (*domain.User, error) {
    user := new(domain.User)

    err := u.DB.Model(user).Where("email = ?", email).First()
    if err != nil {
        if errors.Is(err, pg.ErrNoRows) {
            return nil, domain.ErrNoResult
        }

        return nil, err
    }

    return user, nil
}

func (u *UserRepo) GetByUsername(username string) (*domain.User, error) {
    user := new(domain.User)

    err := u.DB.Model(user).Where("username = ?", username).First()
    if err != nil {
        if errors.Is(err, pg.ErrNoRows) {
            return nil, domain.ErrNoResult
        }

        return nil, err
    }

    return user, nil
}

func (u *UserRepo) Create(user *domain.User) (*domain.User, error) {
    _, err := u.DB.Model(user).Returning("*").Insert()
    if err != nil {
        return nil, err
    }

    return user, nil
}

func NewUserRepo(DB *pg.DB) *UserRepo {
    return &UserRepo{DB: DB}
}

Time to update our main.go with the latest change needed.

// main.go

package main

import (
    "fmt"
    "log"
    "net/http"
    "os"

    "github.com/go-pg/pg/v9"

    "todo/domain"
    "todo/handlers"
    "todo/postgres"
)

func main() {
    DB := postgres.New(&pg.Options{
        User:     "postgres",
        Password: "postgres",
        Database: "todo_dev",
    })

    defer DB.Close()

    domainDB := domain.DB{
        UserRepo: postgres.NewUserRepo(DB),
    }

    d := &domain.Domain{DB: domainDB}

    r := handlers.SetupRouter(d)

    port := os.Getenv("PORT")
    if port == "" {
        port = "8081"
    }

    err := http.ListenAndServe(fmt.Sprintf(":%s", port), r)
    if err != nil {
        log.Fatalf("cannot start server %v", err)
    }
}

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.

Code for this part

Happy Coding :)

This is a cross-platform post from my blog. You can read the original here: https://equimper.com/blog/golang-rest-api-for-nodejs-developer-part-2

Discussion

pic
Editor guide