Plz yurn on JavaScript

Golang Rest API for NodeJS developer - Part 2

November 18, 20192 minutes to read

  • #nodejs
  • #tutorial
  • #golang
Subscribe

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.go
2
3package domain
4
5import "time"
6
7type User struct {
8 ID int64 `json:"id"`
9 Username string `json:"username"`
10 Email string `json:"email"`
11 Password string `json:"-"`
12
13 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.go
2
3package domain
4
5import "errors"
6
7var (
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.go
2package domain
3
4type UserRepo interface {
5 GetByEmail(email string) (*User, error)
6 GetByUsername(username string) (*User, error)
7 Create(user *User) (*User, error)
8}
9
10type DB struct {
11 UserRepo UserRepo
12}
13
14type Domain struct {
15 DB DB
16}

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.go
2package domain
3
4type RegisterPayload struct {
5 Email string `json:"email"`
6 Password string `json:"password"`
7 ConfirmPassword string `json:"confirmPassword"`
8 Username string `json:"username"`
9}
10
11func (d *Domain) Register(payload RegisterPayload) (*User, error) {
12 userExist, _ := d.DB.UserRepo.GetByEmail(payload.Email)
13 if userExist != nil {
14 return nil, ErrUserWithEmailAlreadyExist
15 }
16
17 userExist, _ = d.DB.UserRepo.GetByUsername(payload.Username)
18 if userExist != nil {
19 return nil, ErrUserWithUsernameAlreadyExist
20 }
21
22 password, err := d.setPassword(payload.Password)
23 if err != nil {
24 return nil, err
25 }
26
27 data := &User{
28 Username: payload.Username,
29 Email: payload.Email,
30 Password: *password,
31 }
32
33 user, err := d.DB.UserRepo.Create(data)
34 if err != nil {
35 return nil, err
36 }
37
38 return user, nil
39}
40
41func (d *Domain) setPassword(password string) (*string, error) {
42 return nil, nil
43}

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.go
2
3package handlers
4
5import (
6 "time"
7
8 "github.com/go-chi/chi"
9 "github.com/go-chi/chi/middleware"
10
11 "todo/domain"
12)
13
14type Server struct {
15 domain *domain.Domain
16}
17
18func 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}
27
28func NewServer(domain *domain.Domain) *Server {
29 return &Server{domain: domain}
30}
31
32func SetupRouter(domain *domain.Domain) *chi.Mux {
33 server := NewServer(domain)
34
35 r := chi.NewRouter()
36
37 setupMiddleware(r)
38
39 server.setupEndpoints(r)
40
41 return r
42}

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.go
2
3package handlers
4
5import "net/http"
6
7func (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.go
2
3package postgres
4
5import (
6 "errors"
7
8 "github.com/go-pg/pg/v9"
9
10 "todo/domain"
11)
12
13type UserRepo struct {
14 DB *pg.DB
15}
16
17func (u *UserRepo) GetByEmail(email string) (*domain.User, error) {
18 user := new(domain.User)
19
20 err := u.DB.Model(user).Where("email = ?", email).First()
21 if err != nil {
22 if errors.Is(err, pg.ErrNoRows) {
23 return nil, domain.ErrNoResult
24 }
25
26 return nil, err
27 }
28
29 return user, nil
30}
31
32func (u *UserRepo) GetByUsername(username string) (*domain.User, error) {
33 user := new(domain.User)
34
35 err := u.DB.Model(user).Where("username = ?", username).First()
36 if err != nil {
37 if errors.Is(err, pg.ErrNoRows) {
38 return nil, domain.ErrNoResult
39 }
40
41 return nil, err
42 }
43
44 return user, nil
45}
46
47func (u *UserRepo) Create(user *domain.User) (*domain.User, error) {
48 _, err := u.DB.Model(user).Returning("*").Insert()
49 if err != nil {
50 return nil, err
51 }
52
53 return user, nil
54}
55
56func NewUserRepo(DB *pg.DB) *UserRepo {
57 return &UserRepo{DB: DB}
58}

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

1// main.go
2
3package main
4
5import (
6 "fmt"
7 "log"
8 "net/http"
9 "os"
10
11 "github.com/go-pg/pg/v9"
12
13 "todo/domain"
14 "todo/handlers"
15 "todo/postgres"
16)
17
18func main() {
19 DB := postgres.New(&pg.Options{
20 User: "postgres",
21 Password: "postgres",
22 Database: "todo_dev",
23 })
24
25 defer DB.Close()
26
27 domainDB := domain.DB{
28 UserRepo: postgres.NewUserRepo(DB),
29 }
30
31 d := &domain.Domain{DB: domainDB}
32
33 r := handlers.SetupRouter(d)
34
35 port := os.Getenv("PORT")
36 if port == "" {
37 port = "8081"
38 }
39
40 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.

Code for this part

Happy Coding :)

Previous

Build a REST API with AdonisJs and TDD Part 4

Next

Golang Rest API for NodeJS developer - Part 1

Join my Newsletter

Subscribe to get my latest content & deal for my incoming courses.

    I respect your privacy. Unsubscribe at any time.

    Powered By ConvertKit

    Comments