Creating Hello World web application from scratch

In this step by step tutorial, we are going to create a simple CGI web application from scratch which displays greeting message.

Who is this tutorial for?

This tutorial is written for anyone new to Fano Framework. It is assumed that you have proficiency with Free Pascal and its language syntax. You are familiar with Linux command and and its shell terminal.

Requirement

Application Functionalities

  • Page which display greeting message. If query string name=[a value] is set, for example name=Jon, we will display string ‘Hello Jon’ otherwise we display ‘Hello everybody’.

  • Our application should be accessible via dummy domain name hello-world.fano during development.

Project directory structure

For this project, we will create following directory structures

hello-world
--bin
----unit
--src
----App
------Hello
--------Controllers
----------Factories
--------Views
----------Factories
--tools
--public
----css
----js
--vendor
----fano
  • bin directory will store any binary files generated by compiler.
  • bin/unit will store any compiled unit binaries generated by compiler.
  • src directory will store our application source codes.
  • tools directory will store helper shell scripts.
  • public directory is our application document root which will contains any public assets accessible from client such CSS and JavaScript files.
  • vendor/fano directory will contains Fano Framework source code. This will be created automatically when we setup Fano Framework as git submodule.

In this tutorial, it is assumed that absolute path of project directory is

/home/[your username]/fano-examples/hello-world. So if you login as jon user, then directory is /home/jon/fano-examples/hello-world.

Creating git repository and add Fano Framework repository

After project directory structures created (except vendor/fano), from shell terminal, change directory to hello-world directory and run

$ git init
$ git submodule add https://github.com/fanoframework/fano.git vendor/fano

This will initialize empty git repository and add Fano Framework to hello-world/vendor/fano directory.

Creating main program

Create new file hello-world/src/hello.pas and put following code

program hello;

uses

    fano,
    helloapp;

var
    appInstance : IWebApplication;

begin
    appInstance := TCgiWebApplication.create(
        THelloAppServiceProvider.create(),
        THelloAppRoutes.create()
    );
    appInstance.run();
end.

This will be our main application which will be compiled into executable binary.

Creating hello application unit

Create new file hello-world/src/helloapp.pas and put following code

unit helloapp;

interface

uses

    fano;

type

    THelloAppServiceProvider = class(TBasicAppServiceProvider)
    public
        procedure register(const container : IDependencyContainer); override;
    end;

    THelloAppRoutes = class(TRouteBuilder)
    public
        procedure buildRoutes(
            const container : IDependencyContainer;
            const router : IRouter
        ); override;
    end;

implementation

uses

    sysutils;

    procedure THelloAppServiceProvider.register(const container : IDependencyContainer);
    begin
        //TODO: implement build application dependencies
    end;

    procedure THelloAppRoutes.buildRoutes(
        const container : IDependencyContainer;
        const router : IRouter
    );
    begin
        //TODO: implement build application routes
    end;
end.

Build application dependencies

In order to work, application needs some services to be registered into dependency container. Add following code in register() method

procedure THelloAppServiceProvider.register(const container : IDependencyContainer);
begin
    container.add('viewParams', TViewParametersFactory.create());
end;

In code above, we register service name viewParams, an IViewParameters instance which we will be used to pass data from controller to view. See Displaying Data in View for more information.

Build application routes

A route is basically an association rule between request and code that handles it. Read Working with router for more information.

Add code to buildRoutes() method to register a route.

procedure THelloAppRoutes.buildRoutes(
    const container : IDependencyContainer;
    const router : IRouter
);
begin
    router.get('/', container.get('helloController') as IRequestHandler);
end;

So when user made GET request to http://[our app hostname]/, controller registered as helloController in dependency container will handles it.

Create hello controller

Create a file

hello-world/src/App/Hello/Controllers/HelloController.pas

and put following code

unit HelloController;

interface

uses

    fano;

type

    THelloController = class(TController)
    public
        function handleRequest(
            const request : IRequest;
            const response : IResponse;
            const args : IRouteArgsReader
        ) : IResponse; override;
    end;

implementation

    function THelloController.handleRequest(
        const request : IRequest;
        const response : IResponse;
        const args : IRouteArgsReader
    ) : IResponse;
    var greetName : string;
    begin
        greetName := request.getQueryParam('name', 'everybody');
        fViewParams.setVar('name', greetName);
        result := inherited handleRequest(request, response, args);
    end;

end.

It reads query string name value or use default value of everybody and pass them to view parameters which will get displayed in view.

So for example, http://hello-world.fano?name=jon will cause greetName variable to be filled with value of jon.

fViewParams is protected internal variable of type IViewParameters which is defined inside TController class.

Create hello controller factory class

Create new file

hello-world/src/App/Hello/Controllers/Factories/HelloControllerFactory.pas

And fill its content as follows

unit HelloControllerFactory;

interface

uses
    fano;

type

    THelloControllerFactory = class(TFactory, IDependencyFactory)
    public
        function build(const container : IDependencyContainer) : IDependency; override;
    end;

implementation

uses
    sysutils,

    HelloController;

    function THelloControllerFactory.build(const container : IDependencyContainer) : IDependency;
    begin
        result := THelloController.create(
            container.get('helloView') as IView,
            container.get('viewParams') as IViewParameters
        );
    end;
end.

This factory builds THelloController and pass all dependencies to constructor of the class.

Create hello view

Create new file

hello-world/src/App/Hello/Views/HelloView.pas

with content as follows

unit HelloView;

interface

uses

    fano;

type

    THelloView = class(TInjectableObject, IView)
    public
        function render(
            const viewParams : IViewParameters;
            const response : IResponse
        ) : IResponse;
    end;

implementation

    function THelloView.render(
        const viewParams : IViewParameters;
        const response : IResponse
    ) : IResponse;
    var respBody : IResponseStream;
        greet : string;
    begin
        greet := 'Hello ' + viewParams.getVar('name');
        respBody := response.body();
        respBody.write('<html><head><title>' + greet + '</title></head>');
        respBody.write('<body><p>' + greet + '</p></body></html>');
        result := response;
    end;
end.

It read passed data from controller and build HTML of response.

Create hello view factory class

Create new file

hello-world/src/App/Hello/Views/Factories/HelloViewFactory.pas`

with content as follows

unit HelloViewFactory;

interface

uses
    fano;

type

    THelloViewFactory = class(TFactory)
    public
        function build(const container : IDependencyContainer) : IDependency; override;
    end;

implementation

uses

    SysUtils,
    HelloView;

    function THelloViewFactory.build(const container : IDependencyContainer) : IDependency;
    begin
        result := THelloView.create();
    end;
end.

Register controller and view to dependency container

Edit register() method in file

hello-world/src/helloapp.pas

to become

procedure THelloAppServiceProvider.register(const container : IDependencyContainer);
begin
    container.add('viewParams', TViewParametersFactory.create());
    container.add('helloController', THelloControllerFactory.create());
    container.add('helloView', THelloViewFactory.create());
end;

Also add HelloViewFactory.pas and HelloControllerFactory.pas unit to uses clause of helloapp.pas unit.

uses

    SysUtils,
    HelloControllerFactory,
    HelloViewFactory;

Final hello application unit

This is final content of our helloapp.pas unit

unit helloapp;

interface

uses

    fano;

type

    THelloAppServiceProvider = class(TBasicAppServiceProvider)
    public
        procedure register(const container : IDependencyContainer); override;
    end;

    THelloAppRoutes = class(TRouteBuilder)
    public
        procedure buildRoutes(
            const container : IDependencyContainer;
            const router : IRouter
        ); override;
    end;

implementation

uses

    sysutils;

    procedure THelloAppServiceProvider.register(const container : IDependencyContainer);
    begin
        container.add('viewParams', TViewParametersFactory.create());
        container.add('helloController', THelloControllerFactory.create());
        container.add('helloView', THelloViewFactory.create());
    end;

    procedure THelloAppRoutes.buildRoutes(
        const container : IDependencyContainer;
        const router : IRouter
    );
    begin
        router.get('/', container.get('helloController') as IRequestHandler);
    end;
end.

Creating build script

This is an optional task, but helps simplify task to compile and build application because vendor/fano/fano.cfg contains some environment variables need to be set.

Create new file hello-world/build.sh

#!/bin/bash

export FANO_DIR="vendor/fano"
export USER_APP_DIR="src"
export UNIT_OUTPUT_DIR="bin/unit"
export EXEC_OUTPUT_DIR="public"
export EXEC_OUTPUT_NAME="hello.cgi"
export SOURCE_PROGRAM_NAME="hello.pas"

if [[ -z "${BUILD_TYPE}" ]]; then
export BUILD_TYPE="prod"
fi

fpc @vendor/fano/fano.cfg @build.cfg ${USER_APP_DIR}/${SOURCE_PROGRAM_NAME}

Add execute permission to build.sh

$ chmod +x build.sh

You may want to create shell script to simplify delete all binaries generated by Free Pascal.

Create new file tools/clean.sh and put

#!/bin/bash

find bin/unit ! -name 'README.md' -type f -exec rm -f {} +

Save it and add execute permission to tools/clean.sh

$ chmod +x tools/clean.sh

Create application compiler configuration

Base compiler switch configurations

Create new file hello-world/build.cfg which will contain Free Pascal compiler switches required by application. Please consult Free Pascal documentation for more information about these compiler switches.

-Tlinux
-Fu$USER_APP_DIR$/App/Hello/Controllers
-Fu$USER_APP_DIR$/App/Hello/Controllers/Factories
-Fu$USER_APP_DIR$/App/Hello/Views
-Fu$USER_APP_DIR$/App/Hello/Views/Factories
#INCLUDE build.$BUILD_TYPE$.cfg

Above configuration is basically to generate application for Linux, add controllers and views directory and include another configuration. $USER_APP_DIR$ and $BUILD_TYPE$ is environment variable that we set in build.sh above.

Release compiler switch configuration

Create new file hello-world/build.prod.cfg with content as follows,

-Se
-O3
-Ooregvar
-Oodeadvalues
-CX
-XX
-Xs

This is compiler switches for creating release build which optimized aggresively.

Development compiler switches configuration

Create new file hello-world/build.dev.cfg with content as follows,

-Sewn
-g
-gl
-gh
-Ci
-Cr
-Co
-Ct
-CR
-Sa
-vd
-Vewnh

This are compiler switches to use during development.

Create a .gitignore file

Create file hello-world/.gitignore to ignore all binary generated by Free Pascal compiler and also you may want to ignore any files generated by your text editor. For our project, following code is suffice

public/*.cgi
bin/unit/*

!bin/README.md
!bin/unit/README.md

Final project directories and files

This is our final project directory structures and files.

hello-world
--bin
----unit
--src
----hello.pas
----helloapp.pas
----App
------Hello
--------Controllers
----------HelloController.pas
----------Factories
------------HelloControllerFactory.pas
--------Views
----------HelloView.pas
----------Factories
------------HelloViewFactory.pas
--tools
----clean.sh
--public
----css
----js
--vendor
----fano
--build.sh
--build.cfg
--build.prod.cfg
--build.dev.cfg
--.gitignore

Commit project to git

From inside hello-world directory, add all files and commit them.

$ git add .
$ git commit -m "Initial commit"

If all go well, we are ready to compile the application.

Build application

Assuming you are inside hello-world directory, to build application for production build, run

$ ./build.sh

or

$ BUILD_TYPE=prod ./build.sh

To build application for development build, run

$ BUILD_TYPE=dev ./build.sh

If compilation goes well, we are ready to deploy it.

Deploy application with Apache web server

We will deploy our CGI application, so it can be accessed from web browser via URL http://hello-world.fano. It is assume here that you are login as jon user and our application project path is located at /home/jon/fano-example/hello-world directory. It is assumed that you are using Apache 2.4.

Creating virtual host configuration

If you are in Debian-based distribution, create virtual host by adding Apache configuration file inside /etc/apache2/sites-available and create a file name fano-hello-world.conf.

$ sudo vi /etc/apache2/sites-available/fano-hello-world.conf

Enter you root password and it will open vim text editor. Put following code

<VirtualHost *:80>
    ServerAdmin admin@hello-world.fano
    ServerName hello-world.fano

    DocumentRoot "/home/jon/fano-examples/hello-world/public"

    <Directory "/home/jon/fano-examples/hello-world/public">
        Options -MultiViews +ExecCGI
        AllowOverride FileInfo Indexes
        Require all granted
        AddHandler cgi-script .cgi
        DirectoryIndex hello.cgi

        RewriteEngine On
        RewriteCond %{REQUEST_FILENAME} !-d
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteRule ^(.*)$ hello.cgi [L]

     </Directory>
 </VirtualHost>

Save it and exit. What code above does is tells Apache to allow access to http://hello-world-fano and map it to directory

/home/jon/fano-examples/hello-world/public

For every requests to resources which is actual files and directory, such css and JavaScript files, it tells Apache to serve them, but other resources requested, pass them to our application binary.

If you are in Fedora-based distribution, you may as well just put virtual host configuration in Apache main virtual host configuration /etc/httpd/conf.d/vhost.conf.

Enable virtual host configuration

If you are in Debian-based distribution, you need to enable virtual host configuration and tell Apache to reload configuration by running

$ sudo a2ensite fano-hello-world.conf
$ sudo service apache2 reload

If you are in Fedora-based, you only need to tells Apache to reload configuration

$ sudo systemctl reload apache2

Setup domain name

For our development purpose, we only need to add entry to /etc/hosts files and add following line

127.0.0.1 hello-world.fano

If you want to use real domain name and server, you may want to setup DNS record to point to machine where Apache runs.

Run it from browser

Open web browser and go to http://hello-world.fano, if all go well, you should be greeted with page Hello everybody. If you use URL http://hello-world.fano?name=jon, you should see page Hello jon. If you try to access resources which not exists, ERouteHandlerNotFound exception is triggered.

Summary

In this tutorial, you have learned

  • How to setup web application project directory with Fano Framework from scratch.
  • How to create main application.
  • How to create routes and controller and view and its related factory and,
  • How to connect them with dependency container.
  • How to create shell script to helps build application
  • How to deploy application with Apache web server.

Explore more