Having done this few times before, in Perl, Php, Python, Node.js and C++, it's now time to put my Rust skills to the test with a starter web application, one that has all the ingredients of modern web applications. With this initiative RATTS scampered out.
For context, last year I began exploring Rust and found it to be intriguingly good.
The initial concept was to create a minimalist, deployable and reusable web application that would not compromise on basic security, performance and features. Unsurprisingly, this was accomplished within a few days by utilizing public crates. The application included JWT-based registration and authentication, served with Tokio through an Axum-routed RESTful API, all designed for a responsive UI.
To store user authentication data, a structured database management system was needed and I went for PostgreSQL for its multi-user performance, the sharding and redundancy capabilities - having options is always beneficial, they may come handy. No-SQL solutions are better suited for unstructured data, even if they scale horizontally, while SQLite goes well with low concurrency applications or those that prefer serverless databases, for any reason.
CREATE TYPE authentication.user_status AS ENUM ( 'active', 'suspended', 'deleted' ); CREATE TABLE authentication."user" ( id BIGSERIAL PRIMARY KEY, email TEXT CHECK (char_length(email) <= 254) UNIQUE NOT NULL, password_hash TEXT NOT NULL, status authentication.user_status DEFAULT 'active'::authentication.user_status NOT NULL, create_ts timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, update_ts timestamp with time zone );
The minimum user structure requires an email address and a password hash. The email serves as a unique, human-readable identifier and is used for communicating account-related information to the user. The password hash is essential for authentication purposes. For privacy and security reasons, the password cannot be stored in its original form in the database. Instead, a hash of the password is saved in the user record, which is compared against the hash of the password entered during login. Hashing is an irreversible encryption method that produces unique, fixed-size outputs. In addition to the email and password hash, the user table should include an ID, a state field to disable or mark the account as deleted (pending actual deletion), and timestamps for creation and updates.
is written in Rust and uses Axum, Tokio, and a handful of other crates—approximately 20, each with its own dependency list. The ability to use community-tested code is a considerable productivity boost; however, I fear that migrating crate APIs as versions become obsolete will add to the project's technical debt. If ignored long enough, your code may stop building, and I've seen this happen with Python and Node.js. On the positive side, such API changes usually bring significant security or performance improvements.
use crate::config::load_config; #[tokio::main] async fn main() { println!("Starting RATTS"); let config = load_config().expect("Failed to load config"); let db_pool = match sqlx::postgres::PgPoolOptions::new() .max_connections(10) .connect(&config.database_url) .await { Ok(db_pool) => { println!("Database connection successful"); db_pool } Err(err) => { println!("Database connection failure: {err:?}"); std::process::exit(1); } }; let app_state = state::AppState { config: config.clone(), db: db_pool, // ... }; let router = route::routes(app_state.clone()).with_state(app_state); println!("Listening on http://{}", config.listen_addr); let listener = tokio::net::TcpListener::bind(config.listen_addr).await.unwrap(); axum::serve(listener, router).await.unwrap(); }
I'm gently ambling the C++ elephant into the room, for comparison. The direct reuse of community C++ libraries is often impractical unless you're flexible in terms of licensing, coding principles, style and trust. Often times legacy, niche, secure and/or critical applications are less prone to align with the above. Consequently, the remaining option in C++ is to develop protocol stacks in-house at the expense of far more developer-hours. The result is code guaranteed to build for decades, however applications still require security patches, and the costs of maintenance will not disappear.
Having seen the C++ way, I'm now inclined towards Rust for another reason: consensus. cargo clippy
standardizes code styling with some healthy defaults, besides preventing common mistakes. Add the fact that most crates are MIT licensed and you've eliminated most reasons for starting your next project in C++. Most, because there's one yet unaddressed variable and that's trust! [a debate for another time...]
has full support for TypeScript and that's because the backend's build process was configured to export the set of REST API protocol TS types. Vanilla JS would do as well but unless there are valid reasons in favor, I advice against. Sure, TS transpiles to JS in the end but the added benefit of type safety cannot be ignored.
Angular was chosen for this example because it is among the most popular to date, and it happens to be my favorite. It too follows a similar module management as Node.js in the form of ng
and inherits the same package.json format. It's got clear documentation, good nomenclature leading to good practices and nice coding modularity. Like others, it too suffered breaking API changes between major releases. Something to get used with...
export const routes: Routes = [ { path: '', children: [ { path: '', component: Home }, { path: 'about', component: About }, ] }, { path: '', component: AuthLayout, children: [ { path: 'login', component: Login }, { path: 'register', component: RegistrationSubmitEmail }, { path: 'verify/:token', component: RegistrationVerifyEmail }, ] }, { path: 'user', component: UserLayout, canActivate: [ canActivateUser ], children: [ { path: '', component: UserDashboard }, { path: 'profile', component: UserProfile }, ] }, { path: '**', component: NotFound } ];
The UI relies on a very simple and responsive design. There's a global styles.css imposing the main colors and few element characteristics and each Angular component packs its own, scope limited styling. The RATTS authentication and registration forms are such examples.
All-in-all, this initiative aims to be a simple and comprehensive starter web application in already established technologies - not a one-size-fits-all solution - and I intend on maintaining it indefinitely; maybe add new features deemed essential, here-and-there.