Core concepts

This page describes foundational concepts that are required to be proficient of using Flamego to build web applications that are most optimal.

Classic Flame

The classic Flame instance is the one that comes with a reasonable list of default middleware and could be your starting point for build web applications using Flamego.

A fresh classic Flame instance is returned every time you call flamego.Classicopen in new window, and following middleware are registered automatically:

TIP

If you look up the source code of the flamego.Classicopen in new window, it is fairly simple:

func Classic() *Flame {
	f := New()
	f.Use(
		Logger(),
		Recovery(),
		Static(
			StaticOptions{
				Directory: "public",
			},
		),
	)
	return f
}

Do keep in mind that flamego.Classic may not always be what you want if you do not use these default middleware (e.g. for using custom implementations), or to use different config options, or even just want to change the order of middleware as sometimes the order matters (i.e. middleware are being invoked in the same order as they are registered).

Instances

The function flamego.Newopen in new window is used to create bare Flame instances that do not have default middleware registered, and any type that contains the flamego.Flameopen in new window can be seen as a Flame instance.

Each Flame instace is independent of other Flame instances in the sense that instance state is not shared and is maintained separately by each of them. For example, you can have two Flame instances simultaneously and both of them can have different middleware, routes and handlers registered or defined:

func main() {
	f1 := flamego.Classic()

	f2 := flamego.New()
	f2.Use(flamego.Recovery())

    ...
}

In the above example, f1 has some default middleware registered as a classic Flame instance, while f2 only has a single middleware flamego.Recovery.

💬 Do you agree?

Storing states in the way that is polluting global namespace is such a bad practice that not only makes the code hard to maintain in the future, but also creates more tech debt with every single new line of the code.

It feels so elegent to have isolated state managed by each Flame instance, and make it possible to migrate existing web applications to use Flamego progressively.

Handlers

Flamego handlers are defined as flamego.Handleropen in new window, and if you look closer, it is just an empty interface (interface{}):

// Handler is any callable function. Flamego attempts to inject services into
// the Handler's argument list and panics if any argument could not be fulfilled
// via dependency injection.
type Handler interface{}

As being noted in the docstring, any callable function is a valid flamego.Handler, doesn't matter if it's an anonymous, a declared function or even a method of a type:

package main

import (
	"github.com/flamego/flamego"
)

func main() {
	f := flamego.New()
	f.Get("/anonymous", func() string {
		return "Respond from an anonymous function"
	})
	f.Get("/declared", declared)

	t := &customType{}
	f.Get("/method", t.handler)

	f.Run()
}

func declared() string {
	return "Respond from a declared function"
}

type customType struct{}

func (t *customType) handler() string {
	return "Respond from a method of a type"
}
$ curl http://localhost:2830/anonymous
Respond from an anonymous function

$ curl http://localhost:2830/declared
Respond from a declared function

$ curl http://localhost:2830/method
Respond from a method of a type

Return values

Generally, your web application needs to write content directly to the http.ResponseWriteropen in new window (which you can retrieve using ResponseWriter method of flamego.Contextopen in new window). In some web frameworks, they offer returning an extra error as the indication of the server error as follows:

func handler(w http.ResponseWriter, r *http.Request) error

However, you are still being limited to a designated list of return values from your handlers. In contrast, Flamego provides the flexibility of having different lists of return values from handlers based on your needs case by case, whether it's an error, a string, or just a status code.

Let's see some examples that you can use for your handlers:

package main

import (
	"errors"

	"github.com/flamego/flamego"
)

func main() {
	f := flamego.New()
	f.Get("/string", func() string {
		return "Return a string"
	})
	f.Get("/bytes", func() []byte {
		return []byte("Return some bytes")
	})
	f.Get("/error", func() error {
		return errors.New("Return an error")
	})
	f.Run()
}
$ curl -i http://localhost:2830/string
HTTP/1.1 200 OK
...

Return a string

$ curl -i http://localhost:2830/bytes
HTTP/1.1 200 OK
...

Return some bytes

$ curl -i http://localhost:2830/error
HTTP/1.1 500 Internal Server Error
...

Return an error
...

As you can see, if an error is returned, the Flame instance automatically sets the HTTP status code to be 500.

TIP

Try returning nil for the error on line 18, then redo the test request and see what changes.

Return with a status code

In the cases that you want to have complete control over the status code of your handlers, that is also possible!

package main

import (
	"errors"
	"net/http"

	"github.com/flamego/flamego"
)

func main() {
	f := flamego.New()
	f.Get("/string", func() (int, string) {
		return http.StatusOK, "Return a string"
	})
	f.Get("/bytes", func() (int, []byte) {
		return http.StatusOK, []byte("Return some bytes")
	})
	f.Get("/error", func() (int, error) {
		return http.StatusForbidden, errors.New("Return an error")
	})
	f.Run()
}
$ curl -i http://localhost:2830/string
HTTP/1.1 200 OK
...

Return a string

$ curl -i http://localhost:2830/bytes
HTTP/1.1 200 OK
...

Return some bytes

$ curl -i http://localhost:2830/error
HTTP/1.1 403 Forbidden
...

Return an error
...

Return body with potential error

Body or error? Not a problem!

package main

import (
	"errors"
	"net/http"

	"github.com/flamego/flamego"
)

func main() {
	f := flamego.New()
	f.Get("/string", func() (string, error) {
		return "Return a string", nil
	})
	f.Get("/bytes", func() ([]byte, error) {
		return []byte("Return some bytes"), nil
	})
	f.Run()
}
$ curl -i http://localhost:2830/string
HTTP/1.1 200 OK
...

Return a string

$ curl -i http://localhost:2830/bytes
HTTP/1.1 200 OK
...

Return some bytes

If the handler returns a non-nil error, the error message will be responded to the client instead.

How cool is that?

Service injection

Flamego is claimed to be boiled with dependency injectionopen in new window because of the service injection, it is the soul of the framework. The Flame instance uses the inject.Injectoropen in new window to manage injected services and resolves dependencies of a handler's argument list at the time of the handler invocation.

Both dependency injection and service injection are very abstract concepts, so it is much easier to explain with examples:

// Both `http.ResponseWriter` and `*http.Request` are injected,
// so they can be used as handler arguments.
f.Get("/", func(w http.ResponseWriter, r *http.Request) { ... })

// The `flamego.Context` is probably the most frequently used
// service in your web applications.
f.Get("/", func(c flamego.Context) { ... })

What happens if you try to use a service that hasn't been injected?

package main

import (
	"github.com/flamego/flamego"
)

type myService struct{}

func main() {
	f := flamego.New()
	f.Get("/", func(s myService) {})
	f.Run()
}
http: panic serving 127.0.0.1:50061: unable to invoke the 0th handler [func(main.myService)]: value not found for type main.myService
...

TIP

If you're interested in learning how exactly the service injection works in Flamego, the custom services has the best resources you would want.

Builtin services

There are services that are always injected thus available to every handler, including *log.Loggeropen in new window, flamego.Contextopen in new window, http.ResponseWriteropen in new window and *http.Requestopen in new window.

Middleware

Middleware are the special kind of handlers that are designed as reusable components, and often accepting configurable options. There is no difference between middleware and handlers from compiler's point of view.

Technically speaking, you may use the term middleware and handlers interchangably but the common sense would be that middleware are providing some services, either by injecting to the contextopen in new window or intercepting the requestopen in new window, or both. On the other hand, handlers are mainly focusing on the business logic that is unique to your web application and the route that handlers are registered with.

Middleware can be used at anywhere that a flamego.Handler is accepted, including at global, group and route level.

// Global middleware that are invoked before all other handlers.
f.Use(middleware1, middleware2, middleware3)

// Group middleware that are scoped down to a group of routes.
f.Group("/",
	func() {
		f.Get("/hello", func() { ... })
	},
	middleware4, middleware5, middleware6,
)

// Route-level middleware that are scoped down to a single route.
f.Get("/hi", middleware7, middleware8, middleware9, func() { ... })





 
 
 
 




Please be noted that middleware are always invoked first when a route is matched, i.e. even though that middleware on line 9 appear to be after the route handlers in the group (from line 6 to 8), they are being invoked first regardless.

💡 Did you know?

Global middleware are always invoked regardless whether a route is matched.

TIP

If you're interested in learning how to inject services for your middleware, the custom services has the best resources you would want.

Env

Flamego environment provides the ability to control behaviors of middleware and handlers based on the running environment of your web application. It is defined as the type EnvTypeopen in new window and has some pre-defined values, including flamego.EnvTypeDev, flamego.EnvTypeProd and flamego.EnvTypeTest, which is for indicating development, production and testing environment respectively.

For example, the template middleware rebuilds template files for every request when in flamego.EnvTypeDevopen in new window, but caches the template files otherwise.

The Flamego environment is typically configured via the environment variable FLAMEGO_ENV:

export FLAMEGO_ENV=development
export FLAMEGO_ENV=production
export FLAMEGO_ENV=test

In case you want to retrieve or alter the environment in your web application, Envopen in new window and SetEnvopen in new window methods are also available, and both of them are safe to be used concurrently.