Quick start

The following guide is just a simple example of usage of some of the Iris MVC features. You are not limited to that data structure or code flow.

Create a folder, let's assume its path is app. The structure should look like that:

│   main.go
│   go.mod
│   go.sum
└───environment
│      environment.go
└───model
│      request.go
│      response.go
└───database
│      database.go
│      mysql.go
│      sqlite.go
└───service
│      greet_service.go
└───controller
│      greet_controller.go

Navigate to that app folder and execute the following command:

$ go mod init app
$ go get -u github.com/kataras/iris/v12@main
#                                     or @latest for the latest stable release.

Environment

Let's start by defining the available environments that our web-application can behave on.

We'll just work on two available environments, the "development" and the "production", as they define the two most common scenarios. The ReadEnv will read from the Env type of a system's environment variable (see main.go in the end of the page).

Create a environment/environment.go file and put the following contents:

package environment

import (
    "os"
    "strings"
)

const (
    PROD Env = "production"
    DEV  Env = "development"
)

type Env string

func (e Env) String() string {
    return string(e)
}

func ReadEnv(key string, def Env) Env {
    v := Getenv(key, def.String())
    if v == "" {
        return def
    }

    env := Env(strings.ToLower(v))
    switch env {
    case PROD, DEV: // allowed.
    default:
        panic("unexpected environment " + v)
    }

    return env
}

func Getenv(key string, def string) string {
    if v := os.Getenv(key); v != "" {
        return v
    }

    return def
}

Database

We will use two database management systems, the MySQL and the SQLite. The first one for "production" use and the other for "development".

Create a database/database.go file and copy-paste the following:

package database

import "app/environment"

type DB interface {
    Exec(q string) error
}

func NewDB(env environment.Env) DB {
    switch env {
    case environment.PROD:
        return &mysql{}
    case environment.DEV:
        return &sqlite{}
    default:
        panic("unknown environment")
    }
}

Let's simulate our MySQL and SQLite DB instances. Create a database/mysql.go file which looks like the following one:

package database

import "fmt"

type mysql struct{}

func (db *mysql) Exec(q string) error {
    return fmt.Errorf("mysql: not implemented <%s>", q)
}

And a database/sqlite.go file.

package database

type sqlite struct{}

func (db *sqlite) Exec(q string) error { return nil }

The DB depends on the `Environment.

A practical and operational database example, including Docker images, can be found at the following guide: https://github.com/kataras/iris/tree/main/_examples/database/mysql

Service

We'll need a service that will communicate with a database instance in behalf of our Controller(s).

In our case we will only need a single service, the Greet Service.

For the sake of the example, let's use two implementations of a greet service based on the Environment. The GreetService interface contains a single method of Say(input string) (output string, err error). Create a ./service/greet_service.go file and write the following code:

package service

import (
    "fmt"

    "app/database"
    "app/environment"
)

// GreetService example service.
type GreetService interface {
    Say(input string) (string, error)
}

// NewGreetService returns a service backed with a "db" based on "env".
func NewGreetService(env environment.Env, db database.DB) GreetService {
    service := &greeter{db: db, prefix: "Hello"}

    switch env {
    case environment.PROD:
        return service
    case environment.DEV:
        return &greeterWithLogging{service}
    default:
        panic("unknown environment")
    }
}

type greeter struct {
    prefix string
    db     database.DB
}

func (s *greeter) Say(input string) (string, error) {
    if err := s.db.Exec("simulate a query..."); err != nil {
        return "", err
    }

    result := s.prefix + " " + input
    return result, nil
}

type greeterWithLogging struct {
    *greeter
}

func (s *greeterWithLogging) Say(input string) (string, error) {
    result, err := s.greeter.Say(input)
    fmt.Printf("result: %s\nerror: %v\n", result, err)
    return result, err
}

The greeter will be used on "production" and the greeterWithLogging on "development". The GreetService depends on the Environment and the DB.

Models

Continue by creating our HTTP request and response models.

Create a model/request.go file and copy-paste the following code:

package model

type Request struct {
    Name string `url:"name"`
}

Same for the model/response.go file.

package model

type Response struct {
    Message string `json:"msg"`
}

The server will accept a URL Query Parameter of name (e.g. /greet?name=kataras) and will reply back with a JSON message.

Controller

MVC Controllers are responsible for controlling the flow of the application execution. When you make a request (means request a page) to MVC Application, a controller is responsible for returning the response to that request.

We will only need the GreetController for our mini web-application. Create a file at controller/greet_controller.go which looks like that:

package controller

import (
    "app/model"
    "app/service"
)

type GreetController struct {
    Service service.GreetService
    // Ctx iris.Context
}

func (c *GreetController) Get(req model.Request) (model.Response, error) {
    message, err := c.Service.Say(req.Name)
    if err != nil {
        return model.Response{}, err
    }

    return model.Response{Message: message}, nil
}

The GreetController depends on the GreetService. It serves the GET: /greet index path through its Get method. The Get method accepts a model.Request which contains a single field name of Name which will be extracted from the URL Query Parameter 'name' (because it's a GET requst and its url:"name" struct field). And it will respond with a model.Response (JSON) or an error.

InputsOutputs

Wrap up

                                         +-------------------+
                                         |  Env (DEV, PROD)  |
                                         +---------+---------+
                                         |         |         |
                                         |         |         |
                                         |         |         |
                                    DEV  |         |         |  PROD
-------------------+---------------------+         |         +----------------------+-------------------
                   |                               |                                |
                   |                               |                                |
               +---+-----+        +----------------v------------------+        +----+----+
               | sqlite  |        |         NewDB(Env) DB             |        |  mysql  |
               +---+-----+        +----------------+---+--------------+        +----+----+
                   |                               |   |                            |
                   |                               |   |                            |
                   |                               |   |                            |
    +--------------+-----+     +-------------------v---v-----------------+     +----+------+
    | greeterWithLogging |     |  NewGreetService(Env, DB) GreetService  |     |  greeter  |
    +--------------+-----+     +---------------------------+-------------+     +----+------+
                   |                                       |                        |
                   |                                       |                        |
                   |           +-----------------------------------------+          |
                   |           |  GreetController          |             |          |
                   |           |                           |             |          |
                   |           |  - Service GreetService <--             |          |
                   |           |                                         |          |
                   |           +-------------------+---------------------+          |
                   |                               |                                |
                   |                               |                                |
                   |                               |                                |
                   |                   +-----------+-----------+                    |
                   |                   |      HTTP Request     |                    |
                   |                   +-----------------------+                    |
                   |                   |  /greet?name=kataras  |                    |
                   |                   +-----------+-----------+                    |
                   |                               |                                |
+------------------+--------+         +------------+------------+           +-------+------------------+
|  model.Response (JSON)    |         |  Response (JSON, error) |           |  Bad Request             |
+---------------------------+         +-------------------------+           +--------------------------+
|  {                        |                                               |  mysql: not implemented  |
|    "msg": "Hello kataras" |                                               +--------------------------+
|  }                        |
+---------------------------+

Now it's the time to wrap all the above into our main.go file. Copy-paste the following code:

package main

import (
    "app/controller"
    "app/database"
    "app/environment"
    "app/service"

    "github.com/kataras/iris/v12"
    "github.com/kataras/iris/v12/mvc"
)

func main() {
    app := iris.New()
    app.Get("/ping", pong).Describe("healthcheck")

    mvc.Configure(app.Party("/greet"), setup)

    // http://localhost:8080/greet?name=kataras
    app.Listen(":8080", iris.WithLogLevel("debug"))
}

func pong(ctx iris.Context) {
    ctx.WriteString("pong")
}

func setup(app *mvc.Application) {
    // Register Dependencies.
    app.Register(
        environment.DEV,         // DEV, PROD
        database.NewDB,          // sqlite, mysql
        service.NewGreetService, // greeterWithLogging, greeter
    )

    // Register Controllers.
    app.Handle(new(controller.GreetController))
}

The mvc.Application.Register method registers one more dependencies, dependencies can depend on previously registered dependencies too. Thats the reason we pass, first, the Environment(DEV), then the NewDB which depends on that Environment, following by the NewGreetService function which depends on both the Environment(DEV) and the DB.

The mvc.Application.Handle registers a new controller, which depends on the GreetService, for the targeted sub-router of Party("/greet").

Run

Install Go and run the application with:

go run main.go

Or Docker

Download the Dockerfile and docker-compose.yml files to the app folder.

Install Docker and execute the following command:

$ docker-compose up

Visit http://localhost:8080?name=kataras.

Optionally, replace the main.go's app.Register(environment.DEV with environment.PROD, restart the application and refresh. You will see that a new database (sqlite) and another service of (greeterWithLogging) will be binded to the GreetController. With a single change you achieve to completety change the result.

Last updated