Authentication system using Golang and Sveltekit - Initialization and setup
Tags: go, javascript, svelte, tutorial
Published: 2023-06-02 16:53:45

## Introduction

Following the completion of the series &mdash; [Secure and performant full-stack authentication system using rust (actix-web) and sveltekit][1] and [Secure and performant full-stack authentication system using Python (Django) and SvelteKit][2] &mdash; I felt I should keep the streak by building an equivalent system in PURE go with very minimal external dependencies. We won't use any fancy web framework apart from [httprouter][3] and other basic dependencies including a database driver ([pq][4]), and [redis client][5]. As usual, we'll be using SvelteKit at the front end, favouring JSDoc instead of TypeScript. The combination is ecstatic!

> *NOTE: We will be using some basic ideas from [Let’s Go Further][6] by Alex Edwards. If you are new to Go, such as me, I recommend you start with [Let’s Go][7] and then [Let’s Go Further][6]. The books are great!*

## System's Requirement Specification

Throughout this tutorial series, we'll be working towards implementing these requirements:

> Build a user authentication system where a user authenticates with an E-mail/Password combination. E-mail addresses must be **unique** and **verified** by sending time-limited verification emails upon registration and the verification **emails must support HTML**. **Until verified, no user is allowed to log in**. Time attacks must be addressed by **sending the mails asynchronously**. Password hashing must be strong and only hashed passwords should be stored in the database. Password reset functionality should be incorporated and incepted using e-mail address verifications. A protected user profile update feature should be added so that only authenticated and authorized users can access it. The user profile should include a thumbnail which should be stored in AWS S3.

This is exactly what we implemented in the previous series. We want to learn how it can be done in Go.

> **DECLAIMER: I wasn't paid to promote Alex Edwards' books. Neither am I affiliated. I just found them useful and quite explanatory for beginner and intermediate Go developers alike! I mention them here for reference purposes.**

## Technology stack

For emphasis, our tech stack comprises:

- Backend - Some packages that will be used are:
  - Pure Go (v1.20)
  - [HttpRouter v1][3] - A lightweight high-performance HTTP request router for Go, etc.

- Frontend - Some tools that will be used are:
  - JavaScript - Language in which the frontend will be written
  - [SvelteKit v1][8] - Main frontend framework
  - Pure CSS3 - Styles
  - HTML5 - Structure

## Assumption

A simple prerequisite to follow along is some familiarity with the Go Programming language &mdash; like some understanding of structs, goroutine, module system, and co. &mdash; JavaScript and CSS. You do not need to be an expert &mdash; I ain't one in any of the technologies.

## Source code

The source code for this series is hosted on GitHub via:

{% github Sirneij/go-auth %}

## Implementation

### Step 1: Create a project directory

To start off, create a folder. I called mine `go-auth`, which will house all our source codes.

```shell
~/Documents/Projects/web$ mkdir go-auth && cd go-auth
```

Having created the folder and changed the directory into it, create one subfolder to house the backend codes and another to house the frontend source files:

```shell
~/Documents/Projects/web/go-auth$ mkdir go-auth-backend
~/Documents/Projects/web/go-auth$ npm create svelte@latest frontend
```

The second command creates a SvelteKit project and uses `frontend` as its folder. You should follow the prompts. I chose a skeleton project, opted for JSDoc (not TypeScript), and subscribed to `eslint`, `prettier` and others. You should follow the instructions listed thereafter. For now, we'll be focusing on the backend.

In other to enable the Go module system for the `go-auth-backend` folder, change the directory to it and issue the following command:

```shell
~/Documents/Projects/web/go-auth/go-auth-backend$ go init goauthbackend.johnowolabiidogun.dev
```

`goauthbackend.johnowolabiidogun.dev` is the module's path. You should change this to yours as it is meant to be unique. With that, a `go.mod` file will be created at the root of your directory.

Next, we will create a bunch of directories, a slight variation of this [Go app template][9]:

```shell
~/Documents/Projects/web/go-auth/go-auth-backend$ mkdir -p bin cmd/api internal migrations remote

~/Documents/Projects/web/go-auth/go-auth-backend$ touch Makefile

~/Documents/Projects/web/go-auth/go-auth-backend$ touch cmd/api/main.go
```

By now, we should have a structure like this:

```shell
.
├── Makefile
├── bin
├── cmd
│   └── api
│       └── main.go
├── go.mod
├── go.sum
├── internal
├── migrations
└── remote
```

- `bin` - Ready-to-deploy compiled binaries will be housed here
- `cmd/api/` - Will house most of our route-related codes
- `internal` - Will house our util-related codes including database models and validations, email sending logic and templating, cookie encryption and decryption, custom types, telemetry, token generation and validation, among others.
- `remote` - Production scripts will live here.

The whole file structure idea was drafted from [Let’s Go Further][6]. The book is highly recommended.

Next, we will create some files: `config.go`, `server.go`, and `db.go` in `cmd/api/` to handle our configuration variables, start the Go server, and connect to a PostgreSQL database respectively.

```shell
~/Documents/Projects/web/go-auth/go-auth-backend$ touch cmd/api/config.go cmd/api/server.go cmd/api/db.go
```

### Step2: Connect to PostgreSQL database and Redis store

Having structured our application, it's time to connect to our database and a redis store. Redis is needed for the temporary storage of tokens and cookies. That is surely more performant than storing them in the database.

To begin with, let's create a `config` type in `main.go`. This custom type will be made available to all our routes via another type called `application` by binding the routes as functions to the type. One of Go's paradigms for OOP (Object-oriented Programming):

```go
// cmd/api/main.go
package main

import (
    "os"
    "sync"
    "time"

    _ "github.com/lib/pq"
    "github.com/redis/go-redis/v9"
    "goauthbackend.johnowolabiidogun.dev/internal/jsonlog"
)

const version = "1.0.0"

// `config` type to house all our app's configurations
type config struct {
    port int
    env  string
    db   struct {
        dsn          string
        maxOpenConns int
        maxIdleConns int
        maxIdleTime  string
    }
    redisURL        string
}

// Main `application` type
type application struct {
    config      config
    logger      *jsonlog.Logger
    redisClient *redis.Client
}

func main() {
    logger := jsonlog.New(os.Stdout, jsonlog.LevelInfo)

    cfg, err := updateConfigWithEnvVariables()
    if err != nil {
        logger.PrintFatal(err, nil)
    }

    db, err := openDB(*cfg)

    if err != nil {
        logger.PrintFatal(err, nil)
    }

    defer db.Close()

    logger.PrintInfo("database connection pool established", nil)

    opt, err := redis.ParseURL(cfg.redisURL)
    if err != nil {
        logger.PrintFatal(err, nil)
    }
    client := redis.NewClient(opt)

    logger.PrintInfo("redis connection pool established", nil)

    app := &application{
        config:      *cfg,
        logger:      logger,
        redisClient: client,
    }

    err = app.serve()
    if err != nil {
        logger.PrintFatal(err, nil)
    }
}
```

That must be a lot! Let's go through it.

In the app's entry point, `main()`, we initialized our small logging system. We could have used a third-party logging library. The logging code is in `internal/jsonlog/jsonlog.go`:

```go
// internal/jsonlog/jsonlog.go

package jsonlog

import (
    "encoding/json"
    "io"
    "os"
    "runtime/debug"
    "sync"
    "time"
)

type Level int8

const (
    LevelInfo Level = iota
    LevelError
    LevelFatal
    LevelOff
)

func (l Level) String() string {
    switch l {
    case LevelInfo:
        return "INFO"
    case LevelError:
        return "ERROR"
    case LevelFatal:
        return "FATAL"
    default:
        return ""
    }
}

type Logger struct {
    out      io.Writer
    minLevel Level
    mu       sync.Mutex
}

func New(out io.Writer, minLevel Level) *Logger {
    return &Logger{out: out, minLevel: minLevel}
}

func (l *Logger) PrintInfo(message string, properties map[string]string) {
    l.print(LevelInfo, message, properties)
}
func (l *Logger) PrintError(err error, properties map[string]string) {
    l.print(LevelError, err.Error(), properties)
}
func (l *Logger) PrintFatal(err error, properties map[string]string) {
    l.print(LevelFatal, err.Error(), properties)
    os.Exit(1) // For entries at the FATAL level, we also terminate the application.
}
func (l *Logger) print(level Level, message string, properties map[string]string) (int, error) {
    if level < l.minLevel {
        return 0, nil
    }
    aux := struct {
        Level      string            `json:"level"`
        Time       string            `json:"time"`
        Message    string            `json:"message"`
        Properties map[string]string `json:"properties,omitempty"`
        Trace      string            `json:"trace,omitempty"`
    }{
        Level:      level.String(),
        Time:       time.Now().UTC().Format(time.RFC3339),
        Message:    message,
        Properties: properties,
    }

    if level >= LevelError {
        aux.Trace = string(debug.Stack())
    }

    var line []byte

    line, err := json.Marshal(aux)
    if err != nil {
        line = []byte(LevelError.String() + ": unable to marshal log message: " + err.Error())
    }

    l.mu.Lock()
    defer l.mu.Unlock()
    return l.out.Write(append(line, '\n'))
}

func (l *Logger) Write(message []byte) (n int, err error) {
    return l.print(LevelError, string(message), nil)
}
```

It's some sort of logging system well explained by Alex Edwards in [Let’s Go Further][6]. As stated, we could have used [logrus][10] or any other popular logging system in Go.

Moving on, we tried to get our configurations from `updateConfigWithEnvVariables` function. This function lives in `cmd/api/config.go`:

```go
// cmd/api/config.go

package main

import (
    "encoding/hex"
    "flag"
    "fmt"
    "log"
    "os"
    "strconv"
    "time"

    "github.com/joho/godotenv"
)

func updateConfigWithEnvVariables() (*config, error) {
    // Load environment variables from `.env` file
    err := godotenv.Load(".env", ".env.development")
    if err != nil {
        log.Fatal("Error loading .env file")
    }
    maxOpenConnsStr := os.Getenv("DB_MAX_OPEN_CONNS")
    maxOpenConns, err := strconv.Atoi(maxOpenConnsStr)
    if err != nil {
        log.Fatal(err)
    }
    maxIdleConnsStr := os.Getenv("DB_MAX_IDLE_CONNS")
    maxIdleConns, err := strconv.Atoi(maxIdleConnsStr)
    if err != nil {
        log.Fatal(err)
    }

    var cfg config

    // Basic config
    flag.IntVar(&cfg.port, "port", 8080, "API server port")
    flag.StringVar(&cfg.env, "env", "development", "Environment (development|staging|production)")
    // Database config
    flag.StringVar(&cfg.db.dsn, "db-dsn", os.Getenv("DATABASE_URL"), "PostgreSQL DSN")
    flag.IntVar(&cfg.db.maxOpenConns, "db-max-open-conns", maxOpenConns, "PostgreSQL max open connections")
    flag.IntVar(&cfg.db.maxIdleConns, "db-max-idle-conns", maxIdleConns, "PostgreSQL max idle connections")
    flag.StringVar(&cfg.db.maxIdleTime,
        "db-max-idle-time",
        os.Getenv("DB_MAX_IDLE_TIME"),
        "PostgreSQL max connection idle time",
    )

    // Redis config
    flag.StringVar(&cfg.redisURL, "redis-url", os.Getenv("REDIS_URL"), "Redis URL")

    flag.Parse()

    return &cfg, nil
}
```

In the function, we loaded our `.env` and `.env.development` files using [godotenv][11] which was installed using:

```shell
~/Documents/Projects/web/go-auth/go-auth-backend$ go get github.com/joho/godotenv
```

Having loaded it, we retrieved some of the variables in the files. Since we want our application to be robust enough, we provided an option where you can pass most of the variables as command-line arguments using the [`flag`][12] package. This means that instead of providing `DATABASE_URL` in `.env` file, you can pass it like this:

```shell
~/Documents/Projects/web/go-auth/go-auth-backend$ go run ./cmd/api/ -db-dsn=<url>
```

Pretty neat! We used the [`flag`][12] package to `mutate` the `cfg` variable we created which was later returned if there was no error with parsing the arguments and providing defaults.

Back to the `main()`, we then opened our database connection using `openDB`, a function located in `cmd/api/db.go`:

```go
// cmd/api/db.go
package main

import (
    "context"
    "database/sql"
    "time"

    _ "github.com/lib/pq"
)

func openDB(cfg config) (*sql.DB, error) {
    db, err := sql.Open("postgres", cfg.db.dsn)
    if err != nil {
        return nil, err
    }

    db.SetMaxOpenConns(cfg.db.maxOpenConns)
    db.SetMaxIdleConns(cfg.db.maxIdleConns)

    duration, err := time.ParseDuration(cfg.db.maxIdleTime)
    if err != nil {
        return nil, err
    }

    db.SetConnMaxIdleTime(duration)

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    err = db.PingContext(ctx)
    if err != nil {
        return nil, err
    }

    return db, nil
}
```

Using our [PostgreSQL driver][4] and the built-in `database/sql`, installed with `go get github.com/lib/pq`, we opened connections to a `"postgres"` database and set some parameters including the number of maximum overall connections, idle connections and the duration for idleness. Pretty basic!

Next, in `main()`, we deferred the closure of the connection until our app closes. This is important. Then, using the [recommended redis client for go][5], we parsed our redis URL, set in `.env`. A successful parse returns some options which were then used to initialize a redis client.

These important app-wide variables and connections were then used to initialize our `application`. Doing it this way ensures that they are accessible to all functions bound to the `application` type.

With the initializations done, we needed to start our web server and a method, `serve`, bound to the `application` type helps us achieve this. The method looks like this:

```go
// cmd/api/server.go
package main

import (
    "context"
    "errors"
    "fmt"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func (app *application) serve() error {
    // Declare a HTTP server using the same settings as in our main() function.
    srv := &http.Server{
        Addr:         fmt.Sprintf(":%d", app.config.port),
        Handler:      app.routes(),
        IdleTimeout:  time.Minute,
        ErrorLog:     log.New(app.logger, "", 0),
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 30 * time.Second,
    }

    shutdownError := make(chan error)

    // Start a background goroutine.
    go func() {

        quit := make(chan os.Signal, 1)

        signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

        s := <-quit

        app.logger.PrintInfo("shutting down server", map[string]string{
            "signal": s.String(),
        })

        ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)

        defer cancel()

        err := srv.Shutdown(ctx)
        if err != nil {
            shutdownError <- err
        }

        app.logger.PrintInfo("completing background tasks", map[string]string{
            "addr": srv.Addr})

        app.wg.Wait()

        shutdownError <- nil
    }()

    app.logger.PrintInfo("starting server", map[string]string{
        "addr": srv.Addr,
        "env":  app.config.env,
    })

    err := srv.ListenAndServe()
    if !errors.Is(err, http.ErrServerClosed) {
        return err
    }
    err = <-shutdownError
    if err != nil {
        return err
    }

    app.logger.PrintInfo("stopped server", map[string]string{
        "addr": srv.Addr,
    })

    return nil
}
```

I bet you were bewildered by that code. Is that what you always need to JUST start a Go web server? Definitely not! To start a server in Go, you just need something like this:

```go
func (app *application) serve() error {
    // Declare a HTTP server using the same settings as in our main() function.
    srv := &http.Server{
        Addr:         fmt.Sprintf(":%d", app.config.port),
        Handler:      app.routes(),
        IdleTimeout:  time.Minute,
        ErrorLog:     log.New(app.logger, "", 0),
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 30 * time.Second,
    }
    err := srv.ListenAndServe()
    if err != nil {
        return err
    }
}
```

and you are OKAY! However, we want to build a ROBUST system that won't just shut down at every `CTRL + C` without finishing up pending requests and background tasks. The bulk of the code in `serve()` was for the GRACEFUL shutdown of our application and, again, Alex Edwards delved into it well in [Let’s Go Further][6].

Notice that `serve()` implemented the `application` type. With that implementation, `serve()` can be called anywhere an application instance is available. Hence the reason it was called via `app.serve()` in the `main()` function. This is one way of implementing polymorphism in Go.

### Step 3: Healthcheck route

Our server will refuse to start currently because we fed into its route instance that is non-existent!

```go
..
srv := &http.Server{
        Addr:         fmt.Sprintf(":%d", app.config.port),
        Handler:      app.routes(), // <- Here!
        IdleTimeout:  time.Minute,
        ErrorLog:     log.New(app.logger, "", 0),
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 30 * time.Second,
    }
...
```

Let's fix that by creating a new file, `routes.go`, in `cmd/api` where all our application's routes will live!

```go
// cmd/api/routes.go
package main

import (
    "net/http"

    "github.com/julienschmidt/httprouter"
)

func (app *application) routes() http.Handler {
    router := httprouter.New()

    router.HandlerFunc(http.MethodGet, "/healthcheck/", app.healthcheckHandler)

    return app.recoverPanic(router)
}
```

This is another "method" that implements the `application` interface. It initialized an `httprouter` &mdash; remember to install it via `go get github.com/julienschmidt/httprouter` &mdash; and using its `HandlerFunc` function, we fed the expected HTTP method, `GET` in this case. The resource path, `"/healthcheck/"` and the handler were also supplied. Let's check the hanler out:

```go
// cmd/api/

package main

import (
    "net/http"
)

func (app *application) healthcheckHandler(w http.ResponseWriter, r *http.Request) {
    data := map[string]string{
        "status":      "available",
        "environment": app.config.env,
        "version":     version,
    }

    err := app.writeJSON(w, http.StatusOK, data, nil)

    if err != nil {
        app.serverErrorResponse(w, r, err)
    }
}
```

Go handlers take `http.ResponseWriter` and `http.Request` as arguments and using them, we can perform anything we want! In this case, we returned, in JSON, some basic information about our app using `writeJSON` which was written in `cmd/api/helpers.go`:

```go
// cmd/api/helpers.go
package main

import (
    "encoding/json"
    "net/http"
)

func (app *application) writeJSON(w http.ResponseWriter, status int, data interface{}, headers http.Header) error {
    js, err := json.Marshal(data)

    if err != nil {
        return err
    }

    js = append(js, '\n')

    for key, value := range headers {
        w.Header()[key] = value
    }

    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    w.Write(js)
    return nil
}
```

`writeJSON` is just a basic method that helps convert, using Go's JSON encoder, any type to JSON. It also helps to set appropriate headers and status using the supplied values in the handlers that call it.

In `healthcheckHandler`, we also returned a server error in case there was an error "serializing" the data. This error is defined in `cmd/api/errors.go`:

```go
// cmd/api/errors.go

package main

import (
    "fmt"
    "net/http"
)

type envelope map[string]interface{}

func (app *application) logError(r *http.Request, err error) {
    app.logger.PrintError(err, map[string]string{
        "request_method": r.Method,
        "request_url":    r.URL.String(),
    })
}

func (app *application) errorResponse(w http.ResponseWriter, r *http.Request, status int, message interface{}) {
    env := envelope{"error": message}

    err := app.writeJSON(w, status, env, nil)
    if err != nil {
        app.logError(r, err)
        w.WriteHeader(500)
    }
}

func (app *application) serverErrorResponse(w http.ResponseWriter, r *http.Request, err error) {
    app.logError(r, err)
    message := "the server encountered a problem and could not process your request"
    app.errorResponse(w, r, http.StatusInternalServerError, message)
}
```

`serverErrorResponse` uses `errorResponse` to tell the user about the unexpected issue with our app.  Both of them used `logError` to log the error to our logging console so that we can easily debug it. By now, you should see how beautiful it is with the "polymorphism" we have implemented! We can just use a method anywhere without breaking a sweat!

Back to `routes.go`, we did not just return `router` but `app.recoverPanic(router)`. What is `recoverPanic`? You asked. It's a middleware that does what its name suggests: gracefully recover from panic! We don't want to shut out our users without letting them know there was an issue. 

```go
// cmd/api/middleware.go
package main

import (
    "fmt"
    "net/http"
)

func (app *application) recoverPanic(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                w.Header().Set("Connection", "close")
                app.serverErrorResponse(w, r, fmt.Errorf("%s", err))
            }
        }()
        next.ServeHTTP(w, r)
    })
}
```

That's the middleware. It informs users that something went wrong before closing the connection. That's better if you ask me. With that, you can run the application with:

```shell
~/Documents/Projects/web/go-auth/go-auth-backend$ go run ./cmd/api/
```

Ensure you put the necessary environment variables in `.env` and/or `.env.development`. 

You can visit `http://127.0.0.1:8080/healthcheck/` now if your port is `8080` or `http://127.0.0.1:<port>/healthcheck/`. Congratulations on laying a solid foundation for something great!

We will wrap up this article at this point. In the next one, we'll go into implementing our endpoints properly. See you!

## Outro

Enjoyed this article? I'm a Software Engineer and Technical Writer actively seeking new opportunities, particularly in areas related to web security, finance, health care, and education. If you think my expertise aligns with your team's needs, let's chat! You can find me on LinkedIn: [LinkedIn](https://www.linkedin.com/in/idogun-john-nelson/) and Twitter: [Twitter](https://twitter.com/Sirneij).

If you found this article valuable, consider sharing it with your network to help spread the knowledge!

[1]: https://dev.to/sirneij/series/22690 "Secure and performant full-stack authentication system using rust (actix-web) and sveltekit"
[2]: https://dev.to/sirneij/series/23028 "Secure and performant full-stack authentication system using Python (Django) and SvelteKit"
[3]: https://pkg.go.dev/github.com/julienschmidt/httprouter "HttpRouter is a lightweight high-performance HTTP request router (also called multiplexer or just mux for short) for Go"
[4]: https://pkg.go.dev/github.com/lib/pq "A pure Go Postgres driver for Go's database/sql package"
[5]: https://redis.uptrace.dev/ "Golang Redis client for Redis Server and Redis Cluster"
[6]: https://lets-go-further.alexedwards.net/ "Let’s Go Further"
[7]: https://lets-go.alexedwards.net/ "Let's Go"
[8]: https://kit.svelte.dev/ "web development, streamlined"
[9]: https://github.com/thockin/go-build-template "Go app template build environment"
[10]: https://github.com/Sirupsen/logrus "Structured, pluggable logging for Go."
[11]: https://pkg.go.dev/github.com/joho/godotenv "GoDotEnv"
[12]: https://pkg.go.dev/flag "flag"