Hyper Webapp Template
Like many of us, I’m quite lazy. When making a wep application, lots of the core functionality will be the same from codebase to codebase. You need to respond to HTTP requests, generate and return HTML bodies, serve static assets, handle unknown routes. There’s no pressing need to reinvent all of this from the ground up just to render a new webpage.
This is why we have frameworks, we don’t like doing this stuff over and over again.
However, also like many of us, I’m quite particular. This XKCD comes to mind a lot:
Most CLI scaffolding tools give me this feeling. I get that all the extra boilerplate is actually stuff I want, but I don’t know what it all is.
So, I made my own.
If you’re like me, you won’t use this or any other template. However, if you ARE me, you will, because you built it! Otherwise, it may be helpful for doing your own.
Here’s the GitHub repo. You can click the handy “Use this template” button and get going.
Here’s the highlights:
- Hyper - No framework, just a small and fast HTTP server library.
- Askama - Typesafe, compiled templates.
- TailwindCSS - Granular styling for people who don’t know how to use actual CSS.
- Docker - Simple, quick deployment.
- Github Action - Get a fancy green check mark next to your commits.
Let’s take a quick tour.
One of the oddities (and cool things) is that the assets/ directory actually lives inside src/. This is because all of these text file assets are included right in the binary as static strings via the include_str!() macro. When you deploy, none of this extra stuff is present. The deployment directory will look like this, if Docker is not used:
Just run the thing!
I’ll briefly unpack a few of these files. Let’s look at main.rs first:
The only part of this file you might touch is in the make_service_fn call. This stub assumes your handlers cannot fail and uses std::convert::Infallible. This means that any errors that do pop up in this call (so, your router and handlers) will need to be handled right there, with unwrap() or expect(). You can get yourself a little more flexibility by simply swapping in anyhow::Error! That way, all those unwrap()s can turn into ?s. This is what I’ve done personally when using this template, but I decided not to make that choice for you - that felt like an overreach in a minimal template.
Also, notably, Rust has async/await now! This is very cool syntax for some features (Futures) that have already existed, making the whole thing much much more accessible. No more crazy 120-char types! For a primer, start here.
You won’t really need to touch this. It just sets up the asynchoronous runtime and converts your actual application to a state machine that can use it. In this case, our actual application is the function router(). That looks like this:
All of the handlers eventually pass through to this function:
It takes your response body as a byte slice and compresses it before returning it, adding the proper headers. Lots of resources are going to be HTML, but we’re always using this anyway:
These templates all get a specific struct:
They’re blank for now, there’s no data flowing through. If you wanted to pass a string into the index, it might look like this:
Now you need to instantiate the struct with properly typed data, and you can use name inside your template file.
This template comes pre-hooked up with Tailwind - here’s app.css:
Again, it’s your app, not mine. When you’re ready to style, just start right below these directives - or directly in your templates! The provided NPM scripts will compile all your CSS into src/assets/main.css before compiling the Rust binary, so it too can be included as a static string.
That’s pretty much it! This app is extremely barebones, just how I like my templates. I just successfully used this template to spin up a more complicated application, with a database and some scraping logic, and starting from here instead of from scratch saved me a few hours at the beginning. YMMV.
Stay tuned for two less-minimal variants of this - one for building a static blog, and one with a database and ORM hooked up!
There is definitely room for improvement, here. The code could be refactored (middleware, maybe?), there needs to be tests, etc. I’ll get there eventually, but also, I’ll take a PR :)
Photo by Shiro hatori on Unsplash