Version: v0.9.0

Getting Started

We're going to create our first Kalgan application. We'll see is pretty straightforward to run Kalgan for the first time.

Technical Requirements

Before creating our first Kalgan application we must:

  • Install Rust v1.55 or higher
  • Kalgan has been developed on Linux Operating Systems and all shell scripts documented in the docs have been tested and run on Ubuntu. Everything should work out of the box on Windows as well, but this OS it's not oficcially supported. Compatibility with macOS have not been tested.

Running our first Kalgan app

1. Create the project with cargo:

cargo new project

2. Add the dependency in Cargo.toml (besides, we're going to use feature tera to render HTML templates):

...
[dependencies]
kalgan = { version = "0.9.0", features = ["tera"] }

3. Set the main.rs as follows:

#[macro_use] pub mod macros;
mod controller;

fn main() {
    kalgan::run("settings.yaml", controller::resolver, None);
}

4. Run the app from the project root as follows:

my_path="path_to_my_project" cargo run

5. Open the browser and go to http://127.0.0.1:7878

You will see the following message:

Hello World! :)

The project structure

After executing step 4 we can see that several items have been created inside the project folder. This is because we passed the parameter my_path to the run command, which triggers the build script and creates the basic structure.

This structure should be as follows:

project/
    Cargo.toml
    routes.yaml
    settings.yaml
    src/
        controller.rs
        macros.rs
        main.rs
    target/
        ...
    template/
        hello_world.html

Let’s take a look at some of these files:

routes.yaml

The config .yaml file for the routing system. For the sake of simplicity we only have one route called hello_world which points to / uri and it's linked to the controller hello_world.

routes:
    - hello_world:
        path: /
        controller: hello_world
settings.yaml

The config .yaml file for the app general settings. We have four parameters:

server.address IP/domain of the web server.
server.port Port of the web server.
router.path Path to the routing config file/folder.
tera.path Path to the HTML templates parsed by crate Tera.
server:
    address: 127.0.0.1
    port: 7878
router:
    path: routes.yaml
tera:
    path: template
src/controller.rs

We have two things here:

The resolver which returns the controller is going to process the request. This function is needed as Rust doesn't support runtime reflection.

The controller itself hello_world which basically receives the request sent by the browser (struct kalgan::http::request::Request) and returns a response (struct kalgan::http::response::Response). In this case we are returning a Response with the HTML code of the template hello_world.html using the buil-in macro render!.

use kalgan::http::{ request::Request, response::Response };

pub(crate) fn hello_world(_request: &Request) -> Response {
    render!("hello_world.html")
}
pub(crate) fn resolver(request: &Request, controller: &str) -> Result {
    match controller {
        "hello_world" => Ok(hello_world(request)),
        _ => Err(format!("Controller {} not found in resolver.", &controller))
    }
}
src/macros.rs

A collection of built-in functions to make our code more friendly and a bit less verbose:

render! Calls kalgan::http::response::render with an optional argument for the collection of parameters which are passed to the template.
hashmap! Syntactic sugar to create a std::collections::HashMap based on fat arrow => syntax.

For example: hashmap!("key1" => "value1", "key2" => "value2")
context! Syntactic sugar to create a kalgan::template::Context which contains the parameters that are passed to the template parsed by crate Tera. Same syntax than hashmap!.
url! Calls kalgan::service::url::generate with an optional argument for the collection of parameters to build the URL.
trans! Calls kalgan::service::i18n::trans with an optional argument for the collection of parameters to translate the message.

None of these functions are necessary to work with Kalgan, feel free to get rid of them if you don't feel comfortable with macros.

#[macro_export]
macro_rules! render {
    ($view_path: expr) => {
        kalgan::http::response::render($view_path, kalgan::template::Context::new());
    };
    ($view_path: expr, $view_parameters: expr) => {
        kalgan::http::response::render($view_path, $view_parameters);
    };
}
#[macro_export]
macro_rules! hashmap {
    ($( $key: expr => $val: expr ),*) => {{
         let mut map = ::std::collections::HashMap::new();
         $( map.insert($key, $val); )*
         map
    }}
}
#[macro_export]
macro_rules! context {
    ($( $key: expr => $val: expr ),*) => {{
        use kalgan::template::Sugar;
        let mut context = kalgan::template::Context::new();
        $( context.add($key, $val); )*
        context
    }}
}
#[macro_export]
macro_rules! url {
    ($route_name: expr) => {
        kalgan::service::url::generate($route_name, ::std::collections::HashMap::new());
    };
    ($route_name: expr, $route_parameters: expr) => {
        kalgan::service::url::generate($route_name, $route_parameters);
    };
}
#[macro_export]
macro_rules! trans {
    ($message_name: expr) => {
        kalgan::service::i18n::trans($message_name, ::std::collections::HashMap::new());
    };
    ($message_name: expr, $message_parameters: expr) => {
        kalgan::service::i18n::trans($message_name, $message_parameters);
    };
}
src/main.rs

The file we modified in step 3 to call kalgan::run with the path of the settings file/folder and the controller resolver. We won't need to touch this file very often.

template/

The folder with all the HTML templates which will be parsed by crate Tera. It can be named or located wherever we want, as long as we remember to set the path in the parameter terea.path of our settings file.

template/hello_world.html

The HTML file which is called by the controller hello_world and parsed by crate Tera.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>Hello World! :)</title>
        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    </head>
    <body>
        <p>Hello World! :)</p>
    </body>
</html> 

Scaling up our Kalgan app

We already have a working Kalgan App. However in the real world our app will have to include much more features and therefore components. Our project structure is not suitable for bigger projects and will not scale well. Refactoring is something we always must do for the sake of readability and maintanence.

Here are some advices to refactor the project. Feel free to follow other ways (maybe even better than these ones).

Refactoring the Controllers

At this moment we only have one controller in the project, which is included in the same place than the resolver. But we're going to need more than one controller. Probably we're going to need a lot of them and our src/controller.rs is going to grow too much. That is to say, one of the first things we should do is splitting the controllers.

We're going to create a folder with the same name ( src/controller/ ) and locate the controllers inside. The src/controller.rs will remain with the resolver and the declarations of the controllers:

pub(crate) mod hello_world_controller;
use kalgan::http::{ request::Request, response::Response };

pub(crate) fn resolver(request: &Request, controller: &str) -> Result {
    match controller {
        "hello_world_controller::index" => Ok(hello_world_controller::index(request)),
        _ => Err(format!("Controller {} not found in resolver.", &controller))
    }
}

Several things have changed here. We have renamed the controller hello_world to hello_world_controller. This is just a convention followed by many other web frameworks and this is the way Kalgan takes, but feel free to name your controllers up to your taste. Therefore, we're now calling function index of our controller (again for convention).

Our new controller will have the following aspect:

use kalgan::http::{ request::Request, response::Response };

pub(crate) fn index(_request: &Request) -> Response {
    render!("hello_world.html")
}

And finally, we must amend the controller parameter in the route config file to set the new path:

routes:
- hello_world:
    path: /
    controller: controller/hello_world_controller::index

Refactoring the Configuration Files

Kalgan relies heavily in .yaml configuration files to build the application. This philosophy allows to keep the code away from settings parameters like environment variables, route definitions, messages files (in case you are using the feature i18n) or just your own app settings data.

If we're going to follow this path, we'd better locate these files in a dedicated folder. We should even split these files as some of them may become pretty big.

Therefore we're going to create the folder config/ and inside of it the folders config/app/ and config/routes/. Finally we're going to move the settings.yaml to the former and the routes.yaml to the latter.

We must set the new path of config/app/settings.yaml in src/main.rs:

#[macro_use] pub mod macros;
mod controller;

fn main() {
    kalgan::run("config/app", controller::resolver, None);
}

Notice that we don't need the specify the full path of the file but only it's folder location ( config/app ). Generally this is the way to proceed in Kalgan as we might split our yaml files in multiple ones.

In the same way, we must amend the config/app/settings.yaml to set the new location of config/routes/routes.yaml. But before doing this we're going to rename this file to config/routes/hello_world.yaml as more routes are expected to be created and we might want ot have sepearete route file for echa sectino of our app (for example one for all the endopoints of CRUD users).

Our settings file might look as follows:

server:
    address: 127.0.0.1
    port: 7878
router:
    path: config/routes
tera:
    path: template

Again, notice that we have just specified the route folder location config/route.

If we run the app again we'll get the same result we got in step 5. Keep in mind that everytime we amend a Rust file, that it to say, a controller (we haven't worked with other components yet) we must compile the project. But changes made on .yaml or .html files are reloaded on the fly. This is a very powerful feature which will save us a lot of time while developing.

Adding more components

Now that our project is ready to grow, we can add a second endpoint (for example http://127.0.0.1:7878/foo_bar) which means we must create a new controller ( src/controller/foo_bar_controller.rs ) and a new route ( config/routes/foo_bar.yaml ). We're not going to detail these implementations but we must remember to add the reference of this new controller to the src/controller.rs.

pub(crate) mod hello_world_controller;
pub(crate) mod foo_bar_controller;

use kalgan::http::{ request::Request, response::Response };

pub(crate) fn resolver(request: &Request, controller: &str) -> Result {
    match controller {
        "hello_world_controller::index" => Ok(hello_world_controller::index(request)),
        "foo_bar_controller::index" => Ok(foo_bar_controller::index(request)),
        _ => Err(format!("Controller {} not found in resolver.", &controller))
    }
}

If we have followed the previous steps, our project should present the following structure:

project/
    Cargo.toml
    config/
        app/
            settings.yaml
        routes/
            hello_world.yaml
            foo_bar.yaml
    src/
        controller/
            hello_world_controller.rs
            foo_bar_controller.rs
        controller.rs
        macros.rs
        main.rs
    target/
        ...
    template/
        hello_world.html