In this article, we'll implement and deploy a Gotham full-stack web framework using the Tera template system, Webpack for a complete front-end asset management, a minimal VueJS and CoffeeScript web app and deploy to Heroku. Gotham is a Rust framework which is focused on safety, speed, concurrency and async everything. Webpack is a NodeJS website asset preprocessor and bundler which can let you use any of your favorite front end technologies. Combining these technologies allow for a small footprint on the server, which means saving money on infrastructure, very fast performance in page load for higher visitor retention and the full flexibility of client side code available to you for web design without limitations.
Because there is a lot to unpack here, this article will cover a step-by-step guide to launch these features on Heroku and advise you on common issues that need to be considered.
Installing the dependencies
Before we worry about the server environment, we need to be able to run the server on our own system. You will need to install each of the following:
Since the kind of installation you have to go through depends on your operating system, simply follow the steps provided in the links above for each item.
Setting up the project
First we generate a Rust project with
cargo new mouse cd mouse
We'll call this project mouse, as in Mighty Mouse. Next we'll use WebpackerCli to install the initial files for using Webpacker in our project.
webpacker-cli init
Next we'll edit our Cargo.toml
to add the dependencies we need. Add the following to the end of it.
[dependencies] gotham = "0.3.0" gotham_derive = "0.3.0" hyper = "0.12.13" mime = "0.3.12" lazy_static = "1.2" tera = "0.11" webpacker = "~0.3" [build-dependencies] webpacker = "~0.3"
Now add the following to your build.rs
file in the projects main directory.
extern crate webpacker; fn main() { println!("Validating dependencies…"); assert!(webpacker::valid_project_dir()); println!("Compiling assets…"); let _ = webpacker::compile(); }
Now whenever you run the cargo command to build your project, it will verify your dependencies, bundle and prepare your assets. This is very helpful when you deploy to Heroku as it will tell you which dependencies are missing.
A working project
The hello world example given on the main page for Gotham is as follows. Put this in your src/main.rs
file.
extern crate gotham; use gotham::state::State; const HELLO_WORLD: &'static str = "Hello World!"; pub fn say_hello(state: State) -> (State, &'static str) { (state, HELLO_WORLD) } pub fn main() { let addr = "127.0.0.1:7878"; println!("Listening for requests at http://{}", addr); gotham::start(addr, || Ok(say_hello)) }
At this point you can run cargo run
and use your browser to navigate to http://127.0.0.1:7878
to see the hello world example.
From here we're going to remove the const HELLO_WORLD
line and the entire say_hello
method. We'll add a method named index_page
, add a method named router
, and we'll update the last line of main method to use them.
extern crate gotham; extern crate hyper; use gotham::state::State; use gotham::router::builder::{ build_simple_router, DefineSingleRoute, DrawRoutes }; use gotham::router::Router; use hyper::Method; pub fn index_page(state: State) -> (State, (mime::Mime, String)) { let rendered = "Hello World!".to_string(); (state, (mime::TEXT_HTML, rendered)) } pub fn router() -> Router { build_simple_router(|route| { route. request(vec![Method::GET, Method::HEAD], "/"). to(index_page); }) } pub fn main() { let addr = "127.0.0.1:7878"; println!("Listening for requests at http://{}", addr); gotham::start(addr, router()) }
This changes introduce mime type support in the method which we now use a router to get to. The router is mapping any request to the root url /
to the index_page
method.
For this project we'll follow Rails' outline for organizing the files for the site.
Now to demonstrate how to serve static assets in Gotham. Create the following directory structure app/assets/stylesheets
in the root of your project. Create a file in that last directory named application.css
and give it some styles like so.
div { margin: 0 12px 0 12px; } footer { margin-top: 40px; font-size: 6pt; }
Towards the top of src/main.rs
add use gotham::handler::assets::FileOptions;
and inside the build_simple_router
code block add the following route option after the one you currently have in there.
route. get("style/*"). to_dir( FileOptions::new("app/assets/stylesheets"). with_cache_control("no-cache"). with_gzip(true). build(), );
This will route any requests that try to access the style/
path in the url to any file that's in app/assets/stylesheets
. In our HTML code we'll link to this style directly. Before that though you can now try to load the url http://127.0.0.1:7878/style/application.css
after you run cargo run
and see the styles we've entered in.
We're now ready to introduce HTML pages with the Tera templating system.
Tera templating in Gotham
Tera is a templating DSL for Rust which serializes Rust objects before processing the views. There's very little learning curve to using it as it is designed with common template tasks in mind.
We'll rewrite the index_page
method to now use Tera and include the stylesheet we've created. Also we'll create a core object to load all our templates from and provide it with a path for our views. In your src/main.rs
file update it for the following.
#[macro_use] extern crate lazy_static; extern crate tera; use tera::{Context, Tera}; lazy_static! { pub static ref TERA: Tera = Tera::new("app/views/**/*.tera"). map_err(|e| { eprintln!("Parsing error(s): {}", e); ::std::process::exit(1); }). unwrap(); } pub fn index_page(state: State) -> (State, (mime::Mime, String)) { let mut context = Context::new(); let styles = &["style/application.css"]; let sources: &[&'static str] = &[]; context.insert("application_styles", styles); context.insert("application_sources", sources); let rendered = TERA.render("landing_page/index.html.tera", &context).unwrap(); (state, (mime::TEXT_HTML, rendered)) }
In the lazy_static!
block we create the TERA
object which will load all the views and templates into an internal hash like lookup system and by which we will use it to render views with given contexts. The context we provide a view will contain the objects the view are to be updated or generated with.
Now we need to create our application template and our landing page for the above code to work. Create the file app/views/layouts/application.html.tera
<!DOCTYPE html> <html lang="en"> <head> {% block head %}{% for style in application_styles -%} <link rel="stylesheet" href="{{ style }}" /> {% endfor %}{% for source in application_sources -%} http://%20source%20 <style scoped> p { font-size: 2em; text-align: center; } </style>
Now delete both app/javascript/hello_vue.js
and app/javascript/hello_coffee.coffee
and create the file app/javascript/hello.coffee
and place the following in it.
import Vue from 'vue/dist/vue.esm' import App from '../app.vue' document.addEventListener('DOMContentLoaded', -> element = document.getElementById 'vue-app' if element? app = new Vue( el: element render: (h) -> h App ) # Vue.config.devtools = true )
In my experimentation the environment JavaScript detects it keeps reporting production regardless of changing local environment variables so if you'd like to use the VueJS Devtool addon for your browser you should uncomment the devtools line above while you work.
Now that we have the code to test we need only to include it in our site. Let's change our styles
and sources
values in the index_page
method in src/main.rs
to the following:
let sources = &[ &asset_source("application.js"), &javascript_pack_tag("hello") ]; let styles = &[ "style/application.css", &stylesheet_pack_tag("hello") ];
We're including both the script and style for our VueJS code by using our _pack
method helpers for the hello.coffee
file. Now add the following to app/views/landing_page/index.html.tera
within the content
block.
<div id="vue-app"></div>
And now you have a working VueJS & CoffeeScript app when you run cargo run
and view http://127.0.0.1:7878 .
Deploying to Heroku
To deploy to Heroku you will need to use three separate buildpacks together for NodeJS, Ruby, and Rust. First let's initialize Heroku in our project. Use the Heroku Cli tool:
heroku create heroku buildpacks:add heroku/nodejs heroku buildpacks:add heroku/ruby heroku buildpacks:add emk/rust
It's very important to include Rust as the last buildpack.
Before we can deploy we need to change the way our program hosts it's IP and PORT. Open the src/main.rs
file and change the main
method to the following.
use std::env; pub fn main() { let port = env::var("PORT").expect("PORT env not found!"); let addr = format!("0.0.0.0:{}", port); println!("Listening for requests at {}", addr); gotham::start(addr, router()) }
The application won't work on Heroku without binding to both the address 0.0.0.0
and the port number defined by the environment variable. Now to test it locally you have to assign a port number so the command in bash would look like PORT=7878 cargo run
.
Next we have to let Heroku know what command to run to run the application. Open up a file name Procfile
and place the following.
web: target/release/mouse
The last part is of course the name of the application which we gave it; mouse. Now you need only to commit the source code with git and upload it to heroku.
Be sure your
.gitignore
file has lines fornode_modules
andtmp
as you don't want to upload those.
git add . git commit -m "Heroku ready" git push heroku master
After time enough to brew coffee you can now open the deployed website with the command heroku open
. And viola! You've achieved implementing and deploying a fullstack Gotham app.
As your application grows it will help to organize similar source code categories together in their own separate files (such as moving routing to
src/route.rs
).
Summary
This how-to should save you tons of time figuring out how to get a fullstack Gotham app ready. You can view the source code for this example here on Github.
What you have here is a quicker way to get up and going with a very fast and capable website. Fast being what Rust and Gotham bring to the table and capable being what Webpacker and the entire JavaScript ecosystem bring with it. When you use Rust for your website you get the best performance you can in delivery. Any slowness experienced will be from other factors like unoptimized database queries or network latency. It's exciting to be working with both powerful and performant technologies when delivering content. Enjoy!
Additional resources
Read why
[SaaS CI/CD solutions][14]
can be right for open source projectsUse CodeShip Pro and Traefik for development
Learn how Ruby on Rails can optimize CI testing