Skip to content

wietseva/example-helloworld

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

29 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Flamingo Example Helloworld

Quick reference

Precondition

  • Go >= 1.13 installed

Run the example

git clone git@github.com:i-love-flamingo/example-helloworld.git
cd example-helloworld
go run main.go serve

Open http://localhost:3322

Tutorial Steps

Start Over

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 )

Folder structure

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), // request logger show request logs
		new(gotemplate.Module),    // gotemplate enables the gotemplate template engine module
	})
}

The config file config/config.yaml 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)

In Step 1, we will make sure that the 404 error won't stay for long.

Step 1

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.yaml 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.

Builtin controllers

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.

Step 2

Custom Controller

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:

  1. Kick start a new local Flamingo module helloworld
  2. Write a simple Controller
  3. Register that Controller and a new route in the module
  4. 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), // request logger show request logs
		new(gotemplate.Module),    // gotemplate enables the gotemplate template engine module
		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"
)

// HelloController represents our first simple controller
type HelloController struct {
	responder *web.Responder
}

// Inject dependencies
func (controller *HelloController) Inject(responder *web.Responder) *HelloController {
	controller.responder = responder

	return controller
}

// Get is a controller action that renders the `hello.html` template
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/flamingo/v3/framework/web"

	"flamingo.me/example-helloworld/src/helloworld/interfaces"
)

// 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) {
	// 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 - our internal 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 dependencies - this is called by Dingo and gets an initializes 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.MustRoute("/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.

Step 3

Passing data to templates

  • 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/hello_controller.go and change it as follows:

package interfaces

import (
	"context"

	"flamingo.me/flamingo/v3/framework/web"
)

type (
	// HelloController represents our first simple controller
	HelloController struct {
		responder *web.Responder
	}

	helloViewData struct {
		Name string
	}
)

// Inject dependencies
func (controller *HelloController) Inject(responder *web.Responder) *HelloController{
	controller.responder = responder

	return controller
}

// Get is a controller action that renders the `hello.html` template
func (controller *HelloController) Get(_ 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 ourhello.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.

Step 4

Handle URL parameters

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/hello_controller.go that will use URL parameters.

The Controller uses r.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:

// GreetMe is a controller action that renders the `hello.html` template ands prints a provided URL param
func (controller *HelloController) GreetMe(_ 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 path /hello to a handle with the name "hello"
	registry.MustRoute("/hello", "hello")
    
    // Bind the controller.Action to the handle "hello":
    registry.HandleGet("hello", r.helloController.Get)
    
    registry.HandleGet("helloWorld.greetme", r.helloController.GreetMe)
    registry.MustRoute("/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.

Path Parameters

Beside "GET" parameters we can also add "Path" parameters

  • Extend the module.go, and add another route:
// Bind a route with a path parameter
registry.MustRoute("/greetme/:nickname", "helloWorld.greetme")
  • We can also set default parameters for routes like this:
// Bind a route with a default value for a param
registry.MustRoute("/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:

// 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.MustRoute("/hello", "hello")

	// Bind the controller.Action to the handle "hello":
	registry.HandleGet("hello", r.helloController.Get)

	registry.HandleGet("helloWorld.greetme", r.helloController.GreetMe)
	registry.MustRoute("/greetme", "helloWorld.greetme")
	// Bind a route with a path parameter
	registry.MustRoute("/greetme/:nickname", "helloWorld.greetme")
	// Bind a route with a default value for a param
	registry.MustRoute("/greetflamingo", `helloWorld.greetme(nickname="Flamingo")`)
}

Additionally, we need to adjust our controller action to get the nickname path variable via r.Params.

Go and change it accordingly:

// GreetMe is a controller action that renders the `hello.html` template ands prints a provided URL param
func (controller *HelloController) GreetMe(_ 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:

If something doesn't work, you can always compare your code with the master branch.

Step 5

Requesting Data from DataActions

One of the important things for templates is to be able to "ask for data". There are cases where a controller can't 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 data action:

<html>
    <body>
        <h1>Hello World!</h1>
        <h2>This is the index page</h2>
        <p>Current time: {{ 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 action 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 *web.RouterRegistry) {
	// Bind the path /hello to a handle with the name "hello"
	registry.MustRoute("/hello", "hello")

	// Bind the controller.Action to the handle "hello":
	registry.HandleGet("hello", r.helloController.Get)

	registry.HandleGet("helloWorld.greetme", r.helloController.GreetMe)
	registry.MustRoute("/greetme", "helloWorld.greetme")
	// Bind a route with a path parameter
	registry.MustRoute("/greetme/:nickname", "helloWorld.greetme")
	// Bind a route with a default value for a param
	registry.MustRoute("/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 Hello World Example and learned some basic Flamingo features.

About

Hello World Flamingo Example

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Go 78.0%
  • Makefile 11.7%
  • HTML 10.3%