03, October 2017 | Iris User Experience Report
Be part of the Iris evolution!
Complete the first Iris User Experience Report by submitting a simple form, it won't take more than 2 minutes.
The form contains some questions that you may need to answer in order to learn more about you; learning more about you helps us to serve you with the best possible way!
https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link
Iris
Iris is a fast, simple and efficient micro web framework for Go. It provides a beautifully expressive and easy to use foundation for your next website, API, or distributed app.
Psst, we've produced a small video about your feelings regrating to Iris! You can watch the whole video at https://www.youtube.com/watch?v=jGx0LkuUs4A.
Installation
The only requirement is the Go Programming Language, at least version 1.9
$ go get -u github.com/kataras/iris
Getting Started
package main
import "github.com/kataras/iris"
func main() {
app := iris.New()
app.RegisterView(iris.HTML("./views", ".html"))
app.Get("/", func(ctx iris.Context) {
ctx.ViewData("message", "Hello world!")
ctx.View("hello.html")
})
app.Get("/user/{id:long}", func(ctx iris.Context) {
userID, _ := ctx.Params().GetInt64("id")
ctx.Writef("User ID: %d", userID)
})
app.Run(iris.Addr(":8080"))
}
Learn more about path parameter's types by clicking here.
<html>
<head>
<title>Hello Page</title>
</head>
<body>
<h1>{{.message}}</h1>
</body>
</html>
$ go run main.go
> Now listening on: http://localhost:8080
> Application started. Press CTRL+C to shut down.
Guidelines for bootstrapping handler-based applications can be found at the _examples/structuring/handler-based folder.
Quick MVC Tutorial
package main
import (
"github.com/kataras/iris"
"github.com/kataras/iris/mvc"
)
func main() {
app := iris.New()
app.Controller("/helloworld", new(HelloWorldController))
app.Run(iris.Addr("localhost:8080"))
}
type HelloWorldController struct {
mvc.Controller
}
func (c *HelloWorldController) Get() string {
return "This is my default action..."
}
func (c *HelloWorldController) GetBy(name string) string {
return "Hello " + name
}
func (c *HelloWorldController) GetWelcome() (string, int) {
return "This is the GetWelcome action func...", iris.StatusOK
}
func (c *HelloWorldController) GetWelcomeBy(name string, numTimes int) {
c.Ctx.Writef("Hello %s, NumTimes is: %d", name, numTimes)
}
The _examples/mvc and mvc/controller_test.go files explain each feature with simple paradigms, they show how you can take advandage of the Iris MVC Binder, Iris MVC Models and many more...
Every exported
func prefixed with an HTTP Method(Get
, Post
, Put
, Delete
...) in a controller is callable as an HTTP endpoint. In the sample above, all funcs writes a string to the response. Note the comments preceding each method.
An HTTP endpoint is a targetable URL in the web application, such as http://localhost:8080/helloworld
, and combines the protocol used: HTTP, the network location of the web server (including the TCP port): localhost:8080
and the target URI /helloworld
.
The first comment states this is an HTTP GET method that is invoked by appending "/helloworld" to the base URL. The third comment specifies an HTTP GET method that is invoked by appending "/helloworld/welcome" to the URL.
Controller knows how to handle the "name" on GetBy
or the "name" and "numTimes" at GetWelcomeBy
, because of the By
keyword, and builds the dynamic route without boilerplate; the third comment specifies an HTTP GET dynamic method that is invoked by any URL that starts with "/helloworld/welcome" and followed by two more path parts, the first one can accept any value and the second can accept only numbers, i,e: "http://localhost:8080/helloworld/welcome/golang/32719", otherwise a 404 Not Found HTTP Error will be sent to the client instead.
Quick MVC Tutorial #2
Iris has a very powerful and blazing fast MVC support, you can return any value of any type from a method function
and it will be sent to the client as expected.
- if
string
then it's the body. - if
string
is the second output argument then it's the content type. - if
int
then it's the status code. - if
error
and not nil then (any type) response will be omitted and error's text with a 400 bad request will be rendered instead. - if
(int, error)
and error is not nil then the response result will be the error's text with the status code as int
. - if
bool
is false then it throws 404 not found http error by skipping everything else. - if
custom struct
or interface{}
or slice
or map
then it will be rendered as json, unless a string
content type is following. - if
mvc.Result
then it executes its Dispatch
function, so good design patters can be used to split the model's logic where needed.
The example below is not intended to be used in production but it's a good showcase of some of the return types we saw before;
package main
import (
"github.com/kataras/iris"
"github.com/kataras/iris/middleware/basicauth"
"github.com/kataras/iris/mvc"
)
type Movie struct {
Name string `json:"name"`
Year int `json:"year"`
Genre string `json:"genre"`
Poster string `json:"poster"`
}
var movies = []Movie{
{
Name: "Casablanca",
Year: 1942,
Genre: "Romance",
Poster: "https://iris-go.com/images/examples/mvc-movies/1.jpg",
},
{
Name: "Gone with the Wind",
Year: 1939,
Genre: "Romance",
Poster: "https://iris-go.com/images/examples/mvc-movies/2.jpg",
},
{
Name: "Citizen Kane",
Year: 1941,
Genre: "Mystery",
Poster: "https://iris-go.com/images/examples/mvc-movies/3.jpg",
},
{
Name: "The Wizard of Oz",
Year: 1939,
Genre: "Fantasy",
Poster: "https://iris-go.com/images/examples/mvc-movies/4.jpg",
},
}
var basicAuth = basicauth.New(basicauth.Config{
Users: map[string]string{
"admin": "password",
},
})
func main() {
app := iris.New()
app.Use(basicAuth)
app.Controller("/movies", new(MoviesController))
app.Run(iris.Addr(":8080"))
}
type MoviesController struct {
mvc.C
}
func (c *MoviesController) Get() []Movie {
return movies
}
func (c *MoviesController) GetBy(id int) Movie {
return movies[id]
}
func (c *MoviesController) PutBy(id int) Movie {
m := movies[id]
file, info, err := c.Ctx.FormFile("poster")
if err != nil {
c.Ctx.StatusCode(iris.StatusInternalServerError)
return Movie{}
}
file.Close()
poster := info.Filename
genre := c.Ctx.FormValue("genre")
m.Poster = poster
m.Genre = genre
movies[id] = m
return m
}
func (c *MoviesController) DeleteBy(id int) iris.Map {
deleted := movies[id].Name
movies = append(movies[:id], movies[id+1:]...)
return iris.Map{"deleted": deleted}
}
Quick MVC Tutorial #3
Nothing stops you from using your favorite folder structure. Iris is a low level web framework, it has got MVC first-class support but it doesn't limit your folder structure, this is your choice.
Structuring depends on your own needs. We can't tell you how to design your own application for sure but you're free to take a closer look to one typical example below;
Shhh, let's spread the code itself.
Data Model Layer
package datamodels
type Movie struct {
ID int64 `json:"id"`
Name string `json:"name"`
Year int `json:"year"`
Genre string `json:"genre"`
Poster string `json:"poster"`
}
Data Source / Data Store Layer
package datasource
import "github.com/kataras/iris/_examples/mvc/overview/datamodels"
var Movies = map[int64]datamodels.Movie{
1: {
ID: 1,
Name: "Casablanca",
Year: 1942,
Genre: "Romance",
Poster: "https://iris-go.com/images/examples/mvc-movies/1.jpg",
},
2: {
ID: 2,
Name: "Gone with the Wind",
Year: 1939,
Genre: "Romance",
Poster: "https://iris-go.com/images/examples/mvc-movies/2.jpg",
},
3: {
ID: 3,
Name: "Citizen Kane",
Year: 1941,
Genre: "Mystery",
Poster: "https://iris-go.com/images/examples/mvc-movies/3.jpg",
},
4: {
ID: 4,
Name: "The Wizard of Oz",
Year: 1939,
Genre: "Fantasy",
Poster: "https://iris-go.com/images/examples/mvc-movies/4.jpg",
},
5: {
ID: 5,
Name: "North by Northwest",
Year: 1959,
Genre: "Thriller",
Poster: "https://iris-go.com/images/examples/mvc-movies/5.jpg",
},
}
Repositories
The layer which has direct access to the "datasource" and can manipulate data directly.
package repositories
import (
"errors"
"sync"
"github.com/kataras/iris/_examples/mvc/overview/datamodels"
)
type Query func(datamodels.Movie) bool
type MovieRepository interface {
Exec(query Query, action Query, limit int, mode int) (ok bool)
Select(query Query) (movie datamodels.Movie, found bool)
SelectMany(query Query, limit int) (results []datamodels.Movie)
InsertOrUpdate(movie datamodels.Movie) (updatedMovie datamodels.Movie, err error)
Delete(query Query, limit int) (deleted bool)
}
func NewMovieRepository(source map[int64]datamodels.Movie) MovieRepository {
return &movieMemoryRepository{source: source}
}
type movieMemoryRepository struct {
source map[int64]datamodels.Movie
mu sync.RWMutex
}
const (
ReadOnlyMode = iota
ReadWriteMode
)
func (r *movieMemoryRepository) Exec(query Query, action Query, actionLimit int, mode int) (ok bool) {
loops := 0
if mode == ReadOnlyMode {
r.mu.RLock()
defer r.mu.RUnlock()
} else {
r.mu.Lock()
defer r.mu.Unlock()
}
for _, movie := range r.source {
ok = query(movie)
if ok {
if action(movie) {
if actionLimit >= loops {
break
}
}
}
}
return
}
func (r *movieMemoryRepository) Select(query Query) (movie datamodels.Movie, found bool) {
found = r.Exec(query, func(m datamodels.Movie) bool {
movie = m
return true
}, 1, ReadOnlyMode)
if !found {
movie = datamodels.Movie{}
}
return
}
func (r *movieMemoryRepository) SelectMany(query Query, limit int) (results []datamodels.Movie) {
r.Exec(query, func(m datamodels.Movie) bool {
results = append(results, m)
return true
}, limit, ReadOnlyMode)
return
}
func (r *movieMemoryRepository) InsertOrUpdate(movie datamodels.Movie) (datamodels.Movie, error) {
id := movie.ID
if id == 0 {
var lastID int64
r.mu.RLock()
for _, item := range r.source {
if item.ID > lastID {
lastID = item.ID
}
}
r.mu.RUnlock()
id = lastID + 1
movie.ID = id
r.mu.Lock()
r.source[id] = movie
r.mu.Unlock()
return movie, nil
}
current, exists := r.Select(func(m datamodels.Movie) bool {
return m.ID == id
})
if !exists {
return datamodels.Movie{}, errors.New("failed to update a nonexistent movie")
}
if movie.Poster != "" {
current.Poster = movie.Poster
}
if movie.Genre != "" {
current.Genre = movie.Genre
}
r.mu.Lock()
r.source[id] = current
r.mu.Unlock()
return movie, nil
}
func (r *movieMemoryRepository) Delete(query Query, limit int) bool {
return r.Exec(query, func(m datamodels.Movie) bool {
delete(r.source, m.ID)
return true
}, limit, ReadWriteMode)
}
Services
The layer which has access to call functions from the "repositories" and "models" (or even "datamodels" if simple application). It should contain the most of the domain logic.
package services
import (
"github.com/kataras/iris/_examples/mvc/overview/datamodels"
"github.com/kataras/iris/_examples/mvc/overview/repositories"
)
type MovieService interface {
GetAll() []datamodels.Movie
GetByID(id int64) (datamodels.Movie, bool)
DeleteByID(id int64) bool
UpdatePosterAndGenreByID(id int64, poster string, genre string) (datamodels.Movie, error)
}
func NewMovieService(repo repositories.MovieRepository) MovieService {
return &movieService{
repo: repo,
}
}
type movieService struct {
repo repositories.MovieRepository
}
func (s *movieService) GetAll() []datamodels.Movie {
return s.repo.SelectMany(func(_ datamodels.Movie) bool {
return true
}, -1)
}
func (s *movieService) GetByID(id int64) (datamodels.Movie, bool) {
return s.repo.Select(func(m datamodels.Movie) bool {
return m.ID == id
})
}
func (s *movieService) UpdatePosterAndGenreByID(id int64, poster string, genre string) (datamodels.Movie, error) {
return s.repo.InsertOrUpdate(datamodels.Movie{
ID: id,
Poster: poster,
Genre: genre,
})
}
func (s *movieService) DeleteByID(id int64) bool {
return s.repo.Delete(func(m datamodels.Movie) bool {
return m.ID == id
}, 1)
}
View Models
There should be the view models, the structure that the client will be able to see.
Example:
import (
"github.com/kataras/iris/_examples/mvc/overview/datamodels"
"github.com/kataras/iris/context"
)
type Movie struct {
datamodels.Movie
}
func (m Movie) IsValid() bool {
return m.ID > 0
}
Iris is able to convert any custom data Structure into an HTTP Response Dispatcher,
so theoritically, something like the following is permitted if it's really necessary;
func (m Movie) Dispatch(ctx context.Context) {
if !m.IsValid() {
ctx.NotFound()
return
}
ctx.JSON(m, context.JSON{Indent: " "})
}
However, we will use the "datamodels" as the only one models package because
Movie structure doesn't contain any sensitive data, clients are able to see all of its fields
and we don't need any extra functionality or validation inside it.
Controllers
Handles web requests, bridge between the services and the client.
package controllers
import (
"errors"
"github.com/kataras/iris/_examples/mvc/overview/datamodels"
"github.com/kataras/iris/_examples/mvc/overview/services"
"github.com/kataras/iris"
"github.com/kataras/iris/mvc"
)
type MovieController struct {
mvc.C
Service services.MovieService
}
func (c *MovieController) Get() (results []datamodels.Movie) {
return c.Service.GetAll()
}
func (c *MovieController) GetBy(id int64) (movie datamodels.Movie, found bool) {
return c.Service.GetByID(id)
}
func (c *MovieController) PutBy(id int64) (datamodels.Movie, error) {
file, info, err := c.Ctx.FormFile("poster")
if err != nil {
return datamodels.Movie{}, errors.New("failed due form file 'poster' missing")
}
file.Close()
poster := info.Filename
genre := c.Ctx.FormValue("genre")
return c.Service.UpdatePosterAndGenreByID(id, poster, genre)
}
func (c *MovieController) DeleteBy(id int64) interface{} {
wasDel := c.Service.DeleteByID(id)
if wasDel {
return iris.Map{"deleted": id}
}
return iris.StatusBadRequest
}
package controllers
import (
"errors"
"github.com/kataras/iris/mvc"
)
type HelloController struct {
mvc.C
}
var helloView = mvc.View{
Name: "hello/index.html",
Data: map[string]interface{}{
"Title": "Hello Page",
"MyMessage": "Welcome to my awesome website",
},
}
func (c *HelloController) Get() mvc.Result {
return helloView
}
var errBadName = errors.New("bad name")
var badName = mvc.Response{Err: errBadName, Code: 400}
func (c *HelloController) GetBy(name string) mvc.Result {
if name != "iris" {
return badName
}
return mvc.View{
Name: "hello/name.html",
Data: name,
}
}
package middleware
import "github.com/kataras/iris/middleware/basicauth"
var BasicAuth = basicauth.New(basicauth.Config{
Users: map[string]string{
"admin": "password",
},
})
<html>
<head>
<title>{{.Title}} - My App</title>
</head>
<body>
<p>{{.MyMessage}}</p>
</body>
</html>
<html>
<head>
<title>{{.}}' Portfolio - My App</title>
</head>
<body>
<h1>Hello {{.}}</h1>
</body>
</html>
Navigate to the _examples/view for more examples
like shared layouts, tmpl funcs, reverse routing and more!
Main
This file creates any necessary component and links them together.
package main
import (
"github.com/kataras/iris/_examples/mvc/overview/datasource"
"github.com/kataras/iris/_examples/mvc/overview/repositories"
"github.com/kataras/iris/_examples/mvc/overview/services"
"github.com/kataras/iris/_examples/mvc/overview/web/controllers"
"github.com/kataras/iris/_examples/mvc/overview/web/middleware"
"github.com/kataras/iris"
)
func main() {
app := iris.New()
app.RegisterView(iris.HTML("./web/views", ".html"))
app.Controller("/hello", new(controllers.HelloController))
repo := repositories.NewMovieRepository(datasource.Movies)
movieService := services.NewMovieService(repo)
app.Controller("/movies", new(controllers.MovieController),
movieService,
middleware.BasicAuth)
app.Run(
iris.Addr("localhost:8080"),
iris.WithoutVersionChecker,
iris.WithoutServerError(iris.ErrServerClosed),
iris.WithOptimizations,
)
}
More folder structure guidelines can be found at the _examples/#structuring section.
😃 Do you like what you see so far?
Prepare yourself a cup of coffee, or tea, whatever enjoys you the most!
Take some time, don't say we didn't warn you
, and continue your journey by navigating to the next README page.
License
Iris is licensed under the 3-Clause BSD License. Iris is 100% open-source software.