- Go >= 1.11.4 installed
git clone git@github.com:i-love-flamingo/example-helloworld.git
cd example-helloworld
go run main.go serve
When you checkout the example you already see a very basic flamingo module.
git checkout start-over
Here are some details for the files present in your project ( flamingo.me/example-helloworld
)
First, we start with some basic information on the folder structure:
- config/
- Here we find our configuration and routing files
- src/
- Source code for the project
- templates/
- Templates for Go Templates
- go.mod
- the projects dependencies
- main.go
- Entry point for our project
The main.go
file looks like this
package main
import (
"flamingo.me/dingo"
"flamingo.me/flamingo/v3"
"flamingo.me/flamingo/v3/core/gotemplate"
"flamingo.me/flamingo/v3/core/requestlogger"
"flamingo.me/flamingo/v3/core/zap"
)
// main is our entry point
func main() {
flamingo.App([]dingo.Module{
new(zap.Module), // log formatter
new(requestlogger.Module), // requestlogger show request logs
new(gotemplate.Module), // gotemplate installs a go template engine (in debug mode, todo fix this)
})
}
Also we need a few default templates, they live in templates/error
:
403.html
<h1>Forbidden</h1>
<hr/>
<pre>{{index . "error"}}</pre>
404.html
<h1>File Not Found</h1>
<hr/>
<pre>{{index . "error"}}</pre>
503.html
<h1>Unavailable</h1>
<hr/>
<pre>{{index . "error"}}</pre>
withCode.html
<h1>Error {{index . "code"}}</h1>
<hr/>
<pre>{{index . "error"}}</pre>
We need this setup to make the gotemplate
Module not panic due to missing error-templates at all.
(See Default configuration of the InitModule in Flamingo's framework/module.go
)
The config file config/config.yml
is nearly empty for now. We just enable Flamingo's debug mode.
Now we are ready and can already start with go run main.go
!
You can start the server with go run main.go serve
but you'll be stuck with 404 errors for now. (Obviously, since we do not have any routes registered.)
The flamingo default app runs on port 3322, so go and visit http://localhost:3322/
You'll see log-output like
2019-07-26T13:42:10.073+0200 INFO requestlogger/logger.go:131 GET / 404: 42b in 5.788304ms (Error: action for method "GET" not found and no any fallback) {"area": "root", "traceID": "7158d0b6f0214ac018ccd0b6241da2f3", "spanID": "f16b1510928ff8df", "method": "GET", "path": "/", "client_ip": "[::1]:52572", "businessId": "", "accesslog": 1, "response_code": 404, "response_time": 0.005788304, "referer": ""}
In Step 1, we will make sure that the 404 error won't stay for long.
You can either start with your current code or you can just checkout the branch "start-over" with
git checkout start-over
At first, we create a template to be shown when visiting the page.
Create a template file called index.html
in the templates
folder with basic content:
<html>
<body>
<h1>Hello World!</h1>
<h2>This is the index page</h2>
</body>
</html>
But to see this page in the browser, we need some routing.
Create the file config/routes.yml
and add the route:
- path: /
name: index
controller: flamingo.render(tpl="index")
This is the basic route on path "/" and it renders the "index" template via the builtin flamingo.render controller.
Flamingo comes with a couple of builtin controllers, that we can use in routes.yml
. E.g.:
- flamingo.render(tpl="...")
- Default template renderer
- flamingo.redirect(to="...") and flamingo.redirectPermanent(to="...")
- Default "redirect to other route" controller
- flamingo.redirectUrl(url="...") and flamingo.redirectPermanentUrl(url="...")
- Default "redirect to a URL" controller
- flamingo.error and flamingo.notfound
- Default Flamingo error and notfound controller
Now, you can start the server again with go run main.go serve
and see the result in the browser when you visit http://localhost:3322/.
If something doesn't work, you can always compare your code with the master branch.
In this step we will create our first own controller and route.
- In Flamingo this is done with a custom module.
- We place our custom module in
src/helloworld/module.go
We will now learn step by step how to:
- Kickstart a new local Flamingo module
helloworld
- Write a simple Controller
- Register that Controller and a new route in the module
- Add another template
Let us start with the helloworld
module:
Create a new file module.go
inside src/helloworld
with:
package helloworld
import (
"flamingo.me/dingo"
)
// Module is our helloWorld Module
type Module struct {}
// Configure is the default Method a Module needs to implement
func (m *Module) Configure(injector *dingo.Injector) {
// ...
}
Our Module is a dingo.Module
, which can be used in our Flamingo project. To load our own Module we need to add a line
in the main.go file. So open your project main.go
and add the new Module to the Bootstrap, it should look like this:
package main
import (
"flamingo.me/dingo"
"flamingo.me/flamingo/v3"
"flamingo.me/flamingo/v3/core/gotemplate"
"flamingo.me/flamingo/v3/core/requestlogger"
"flamingo.me/flamingo/v3/core/zap"
"flamingo.me/example-helloworld/src/helloworld"
)
// main is our entry point
func main() {
flamingo.App([]dingo.Module{
new(zap.Module), // log formatter
new(requestlogger.Module), // requestlogger show request logs
new(gotemplate.Module), // gotemplate installs a go template engine
new(helloworld.Module),
})
}
Now we create an own controller, first we start with some information about controllers:
- A Controller in Flamingo is a struct with a couple of methods, used as „Actions“
- Controllers can use the
*web.Responder
to create responses - We can also simply use standard http.Handler from Go's stdlib
Create a folder interfaces
in your helloworld
Module, then create a go file called hello_controller.go
in the
interfaces
folder with the following content:
package interfaces
import (
"context"
"flamingo.me/flamingo/v3/framework/web"
)
type HelloController struct {
responder *web.Responder
}
func (controller *HelloController) Inject(responder *web.Responder) *HelloController {
controller.responder = responder
return controller
}
func (controller *HelloController) Get(ctx context.Context, r *web.Request) web.Result {
// Calling the Render method from the response helper and render the template "hello"
return controller.responder.Render("hello", nil)
}
Our Controller reacts on GET requests and renders templates.
Beside Render()
there are more functions such as Data()
, RouteRedirect()
and NotFound()
.
Now, we have to register our controller and a route in our module.go
file:
package helloworld
import (
"flamingo.me/dingo"
"flamingo.me/hello-flamingo/src/helloworld/interfaces"
"flamingo.me/flamingo/v3/framework/web"
)
// Module is our helloWorld Module
type Module struct{}
// Configure is the default Method a Module need to implement
func (m *Module) Configure(injector *dingo.Injector) {
//Call Bind helper of router Module
// It is a shortcut for: injector.BindMulti((*router.Module)(nil)).To(new(routes))
// So what it does is register our routes struct as a router Module - so that it is "known" to the router module
web.BindRoutes(injector, new(routes))
}
// routes struct that gets the interface methods for router.Module
type routes struct {
// helloController - we will defined routes that are handled by our HelloController - so we need this as a dependency
helloController *interfaces.HelloController
}
// Inject method is called by Dingo and gets an initialized instance of the HelloController passed automatically
func (r *routes) Inject(controller *interfaces.HelloController) *routes {
r.helloController = controller
return r
}
// Routes method which defines all routes handlers in module
func (r *routes) Routes(registry *web.RouterRegistry) {
// Bind the path /hello to a handle with the name "hello"
registry.Route("/hello", "hello")
// Bind the controller.Action to the handle "hello":
registry.HandleGet("hello", r.helloController.Get)
}
We also need a matching template, so we create a template for our own controller.
Create a template file called hello.html
in the templates
folder with some simple content:
<h1>Hello Controller</h1>
Now, you can start the server again with go run main.go serve
and on http://localhost:3322/hello we will now
have our custom controller, that renders the new template.
If something doesn't work, you can always compare your code with the master branch.
- It is important for controllers to be able to pass data into templates
- This is done via the render call, as the second parameter (which is currently nil)
First we create a new struct, helloViewData, which we use as the data transfer object for the data that should be available in the template.
We will then pass this data to our template.
For all that, we need to navigate to the file src/helloworld/interfaces/helloController.go and change it as follows:
package interfaces
import (
"context"
"flamingo.me/flamingo/v3/framework/web"
)
type (
HelloController struct {
responder *web.Responder
}
helloViewData struct {
Name string
}
)
func (controller *HelloController) Inject(responder *web.Responder) *HelloController {
controller.responder = responder
return controller
}
func (controller *HelloController) Get(ctx context.Context, r *web.Request) web.Result {
// Calling the Render method from the response helper and render the template "hello"
return controller.responder.Render("hello", helloViewData{
Name: "World",
})
}
As you can see in the Get
function, we pass the variable Name
with the value World
to the ´hello.htmltemplate. To use that variable now in our
hello.html` template, we need to change the template accordingly:
<h1>Hello {{ .Name }}</h1>
Now, you can start the server again with go run main.go serve
and see the result in the browser when you visit http://localhost:3322/hello.
Now http://localhost:3322/hello will show "Hello World", where the "World" part is the string passed in the controller.
If something doesn't work, you can always compare your code with the master branch.
Now it's time to handle URL Parameters
There are 3 ways of getting data into the controller:
- POST/Form data
- GET Parameter
- URL Path parameter
Create a new Action GreetMe
in your src/helloworld/interfaces/helloController.go
that will use URL parameters.
The Controller uses c.Query1 to get the first GET Query parameter with the key name
.
It will return an error if not set, so we can default it to something we want if it's not set.
We will let it default to "World (default)" for now:
func (controller *HelloController) GreetMe(ctx context.Context, r *web.Request) web.Result {
name, err := r.Query1("name")
if err != nil {
name = "World (default)"
}
return controller.responder.Render("hello", helloViewData{
Name: name,
})
}
Now we need to add new greetme
routes in the src/helloworld/module.go
:
// Routes method which defines all routes handlers in module
func (r *routes) Routes(registry *web.RouterRegistry) {
//Bind the controller.Action to the handle "hello":
registry.HandleGet("helloWorld.hello", r.helloController.Get)
//Bind the path /hello to a handle with the name "hello"
registry.Route("/hello", "helloWorld.hello")
registry.HandleGet("helloWorld.greetme", r.helloController.GreetMe)
registry.Route("/greetme", "helloWorld.greetme")
}
Now, you can start the server again with go run main.go serve
and see the result in the browser when you visit http://localhost:3322/greetme?name=Flamingo.
Now http://localhost:3322/greetme?name=Flamingo will show "Hello Flamingo", where the "Flamingo" part is the "name" parameter passed from the URL via the controller to the template.
Beside "GET" parameters we can also add "Path" parameters
- Extend the module.go, and add another route:
registry.Route("/greetme/:nickname", "helloWorld.greetme")
- We can also set default parameters for routes like this:
registry.Route("/greetflamingo", `helloWorld.greetme(nickname="Flamingo")`)
Add both routes to the src/helloworld/module.go
file.
So the routes in the src/helloworld/module.go
now should look like this:
func (r *routes) Routes(registry *router.RouterRegistry) {
//Bind the controller.Action to the handle "hello":
registry.HandleGet("helloWorld.hello", r.helloController.Get)
//Bind the path /hello to a handle with the name "hello"
registry.Route("/hello", "helloWorld.hello")
registry.HandleGet("helloWorld.greetme", r.helloController.GreetMe)
registry.Route("/greetme", "helloWorld.greetme")
registry.Route("/greetme/:nickname", "helloWorld.greetme")
registry.Route("/greetflamingo", `helloWorld.greetme(nickname="Flamingo")`)
}
Additionally, we need to adjust our controller so we can get the nickname
path variable via r.Params
.
Go and change it accordingly:
func (controller *HelloController) GreetMe(ctx context.Context, r *web.Request) web.Result {
name, err := r.Query1("name")
if err != nil {
name = "World (default)"
}
nick, _ := r.Params["nickname"]
return controller.responder.Render("hello", helloViewData{
Name: name,
Nickname: nick,
})
}
We also need to extend the helloViewData struct, so add the "Nickname":
helloViewData struct {
Name string
Nickname string
}
Change the "hello.html" template to include the nickname:
<h1>Hello {{ .Name }}</h1>
{{ if .Nickname }}
<h2>Your Nickname is {{ .Nickname }} </h2>
{{end}}
Now, you can start the server again with go run main.go serve
and see the result in the browser when you visit:
- http://localhost:3322/greetme/awesome?name=Flamingo
- This should show "Flamingo" as "Name" and "awesome" as "Nickname".
- http://localhost:3322/greetflamingo
- This should show "World (default)" as "Name" and "Flamingo" as "Nickname".
If something doesn't work, you can always compare your code with the master branch.
One of the important things for templates is to be able to "ask for data". There are cases where a controller can not simply create all data for a view, instead the view/template should be able to request data. DataActions work similar to normal Actions, but are called from within templates. So let us check how we can request data.
First in our templates/index.html file we call the templatefunc „data“ to access a Datacontroller:
<html>
<body>
<h1>Hello World!</h1>
<h2>This is the index page</h2>
<p>Currenttime: {{ data "currenttime" }}</p>
</body>
</html>
Running go run main.go serve
and opening
http://localhost:3322/
will now show an exception, because we do not
actually have a data controller with the name currenttime
. We need to add a data action to our helloController:
// CurrentTime is a DataAction that handles data calls from templates
func (controller *HelloController) CurrentTime(ctx context.Context, r *web.Request, callParams web.RequestParams) interface{} {
return time.Now().Format(time.RFC822)
}
After that, we register it as Data Handler in our module.go
, but without a route (though we could also route it if necessary):
registry.HandleData("currenttime", r.helloController.CurrentTime)
So the routes part in the module.go should look like this:
// Routes method which defines all routes handlers in module
func (r *routes) Routes(registry *router.Registry) {
//Bind the controller.Action to the handle "hello":
registry.HandleGet("helloWorld.hello", r.helloController.Get)
//Bind the path /hello to a handle with the name "hello"
registry.Route("/hello", "helloWorld.hello")
registry.HandleGet("helloWorld.greetme", r.helloController.GreetMe)
registry.Route("/greetme", "helloWorld.greetme")
registry.Route("/greetme/:nickname", "helloWorld.greetme")
registry.Route("/greetflamingo", `helloWorld.greetme(nickname="Flamingo")`)
registry.HandleData("currenttime", r.helloController.CurrentTime)
}
Now we can run flamingo again and we have the data from the data action available in our template. The DataAction has access to the request and session data, so it can return everything necessary.
If something doesn't work, you can always compare your code with the master branch.
Congratulations! You completed all steps in the Flamingo Helloworld Example and learned some basic Flamingo features.