The author of the material, the translation of which we are publishing today, says that his most recent experiment in the area of software projects architecture was the creation of a working web application using only Rust language and with the least possible use of the template code. In this material, he wants to share with readers what he found out when developing the application and answering the
question of whether Rust is ready to use it in various areas of web development.

Project Overview
The project code, which will be discussed here, can be found on
GitHub . The client and server parts of the application are located in the same repository, this is done to simplify project maintenance. It should be noted that Cargo will need to compile the frontend and backend applications with different dependencies.
Here you can take a look at the running application.
Our project is a simple demonstration of the authentication mechanism. It allows you to log in with the selected username and password (they must be the same).
If the name and password are different, authentication will fail. After successful authentication, the
JWT (JSON Web Token) token is stored both on the client side and on the server side. Storing a token on a server in such applications is usually not required, but I did just that for demonstration purposes. This, for example, can be used to find out how many users are logged in. The entire application can be configured using a single
Config.toml file, for example, specifying credentials for accessing the database, or the address and port number of the server. Here is what the standard code of this file looks like for our application.
[server] ip = "127.0.0.1" port = "30080" tls = false [log] actix_web = "debug" webapp = "trace" [postgres] host = "127.0.0.1" username = "username" password = "password" database = "database"
Client application development
To develop the client side of the application, I decided to use
yew . This is a modern Rust framework, inspired by Elm, Angular and React. It is designed to create client parts of multi-threaded web applications using
WebAssembly (Wasm). At the moment, this project is under active development, while there are not many stable releases of it.
The
yew framework relies on the
cargo-web tool, which is designed to cross-compile code into Wasm.
The
cargo-web tool is a direct
yew dependency that simplifies the cross-compilation of the Rust code in Wasm. Here are the three main goals of the Wasm compilation available under this tool:
asmjs-unknown-emscripten - uses asm.js via Emscripten.wasm32-unknown-emscripten - uses WebAssembly via Emscriptenwasm32-unknown-unknown - uses WebAssembly using the native Rust backend for WebAssembly
WebAssemblyI decided to use the latter option, which requires the use of the Rust compiler’s “nightly” build, but at its best it demonstrates the native Wasm-capabilities of Rust.
If we talk about WebAssembly, then in conversations about Rust today it is the hottest topic. At the moment, there is a lot of work involved in cross-compiling Rust into Wasm and integrating it into the Node.js ecosystem (using npm-packages). I decided to implement the project without any JavaScript dependencies.
When you launch the frontend of a web application (in my project this is done with the
make frontend command),
cargo-web performs a cross-compilation of the application into Wasm and packages it, adding some static materials. Then,
cargo-web launches a local web server that allows you to interact with the application for development purposes. Here is what happens in the console when you run the above command:
> make frontend Compiling webapp v0.3.0 (file:///home/sascha/webapp.rs) Finished release [optimized] target(s) in 11.86s Garbage collecting "app.wasm"... Processing "app.wasm"... Finished processing of "app.wasm"! If you need to serve any extra files put them in the 'static' directory in the root of your crate; they will be served alongside your application. You can also put a 'static' directory in your 'src' directory. Your application is being served at '/app.js'. It will be automatically rebuilt if you make any changes in your code. You can access the web server at `http://0.0.0.0:8000`.
The
yew framework has some very interesting features. Among them - support for the architecture of components suitable for reuse. This feature has simplified splitting my application into three main components:
RootComponent . This component is directly mounted to the
<body> tag of the website. It decides which child component should be loaded next. If, at the first login to the page, the JWT token is found, it tries to update this token by contacting the server part of the application. If this fails, the transition to the
LoginComponent component is
LoginComponent .
LoginComponent . This component is a descendant of the
RootComponent component, it contains a form with fields for entering credentials. In addition, it interacts with the application backend to create a simple authentication scheme based on checking the user name and password, and, in case of successful authentication, stores the JWT in a cookie. In addition, if the user was able to authenticate, he moves to the
ContentComponent component.
Appearance of the component LoginComponentContentComponent . This component is another descendant of the
RootComponent component. It contains what is displayed on the main page of the application (at the moment it is just a title and a button to logout). Access to it can be obtained through the
RootComponent (if the application, at startup, managed to find a valid session token), or through the
LoginComponent (in case of successful authentication). This component communicates with the backend when the user presses the logout button.
ContentComponent componentRouterComponent . This component stores all possible routes between components containing content. In addition, it contains the initial state of the application
loading and
error . It is directly connected to the
RootComponent .
One of the following key concepts of
yew , which we will discuss right now, is services. They allow you to reuse the same logic in different components. Let's say it can be logging interfaces or means to support the work with
cookies . Services do not store a certain global state; they are created when components are initialized. In addition to services,
yew supports the concept of agents. They can be used to organize the sharing of data by various components, to maintain the overall state of the application, such as the one needed for the agent responsible for routing. To organize the routing system of our application, covering all the components,
our own agent and routing service were implemented here. There
yew no standard router in
yew , but in the framework repository you can find
an example implementation of a router that supports a variety of URL operations.
I am pleased to note that
yew uses
the Web Workers API to run agents in different threads and uses a local scheduler attached to the thread to solve parallel tasks. This makes it possible to develop browser applications with a high degree of multi-threading on Rust.
Each component implements its own
Renderable type, which allows us to include HTML-code directly into the source code on Rust, using the macro
html! {} .
The possibility is wonderful, and, of course, the compiler controls its proper use. Here is the
Renderable implementation
Renderable in the
LoginComponent component.
impl Renderable<LoginComponent> for LoginComponent { fn view(&self) -> Html<Self> { html! { <div class="uk-card uk-card-default uk-card-body uk-width-1-3@s uk-position-center",> <form onsubmit="return false",> <fieldset class="uk-fieldset",> <legend class="uk-legend",>{"Authentication"}</legend> <div class="uk-margin",> <input class="uk-input", placeholder="Username", value=&self.username, oninput=|e| Message::UpdateUsername(e.value), /> </div> <div class="uk-margin",> <input class="uk-input", type="password", placeholder="Password", value=&self.password, oninput=|e| Message::UpdatePassword(e.value), /> </div> <button class="uk-button uk-button-default", type="submit", disabled=self.button_disabled, onclick=|_| Message::LoginRequest,>{"Login"}</button> <span class="uk-margin-small-left uk-text-warning uk-text-right",> {&self.error} </span> </fieldset> </form> </div> } } }
The connection between the front end and the back end is based on
WebSocket connections, which are used by each client. The strength of the WebSocket technology is the fact that it is suitable for transmitting binary messages, as well as the fact that the server, if necessary, can send push notifications to clients. In
yew there is a standard WebSocket service, but I decided to create its
own version for demonstration purposes, mainly because of the “lazy” initialization of connections right inside the service. If the WebSocket service were created during component initialization, I would have to track multiple connections.
Protocol Cap'n ProtoI decided to use the
Cap'n Proto protocol (instead of something like
JSON ,
MessagePack or
CBOR ) as the data transfer layer of the application for speed and compactness. It is worth noting that I did not use the
RPC protocol interface that Cap'n Proto has, since its Rust implementation is not compiled for WebAssembly (due to the Unix dependencies of
tokio-rs ). This somewhat complicated the selection of requests and responses of the correct types, but this problem can be solved with the help of a
clearly structured API . Here is the Cap'n Proto protocol declaration for the application.
@0x998efb67a0d7453f; struct Request { union { login :union { credentials :group { username @0 :Text; password @1 :Text; } token @2 :Text; } logout @3 :Text; # The session token } } struct Response { union { login :union { token @0 :Text; error @1 :Text; } logout: union { success @2 :Void; error @3 :Text; } } }
You can see that here we have two different options for a login request.
One is for the
LoginComponent (here, to get a token, the name and password are used), and another one for the
RootComponent (it is used to update an already existing token). All that is needed for the operation of the protocol is packaged in the
protocol service, thanks to which the corresponding capabilities can be conveniently reused in various parts of the frontend.
UIkit is a compact modular front-end framework for developing fast and powerful web interfaces.The client interface of the application is based on the
UIkit framework, its version 3.0.0 will be released in the near future. The specially prepared
build.rs script automatically loads all the necessary UIkit dependencies and compiles the resulting stylesheet. This means that you can add your own styles to a single file,
style.scss , that can be applied across the entire application. It is very convenient.
Test front testing
I believe that there are some problems with testing our solution. The fact is that it is very easy to test individual services, but
yew does not provide a developer with a convenient way to test components and agents. Now, within the framework of pure Rust, integration and end-to-end testing of the frontend is not available. You could use projects like
Cypress or
Protractor here , but with this approach you would have to include a lot of sample JavaScript / TypeScript code in the project, so I decided to abandon the implementation of such tests.
By the way, here's an idea for a new project: the end-to-end testing framework written in Rust.
Development of the server side of the application
To implement the server side of the application, I chose the
actix-web framework. It is a compact, practical and very fast Rust framework based on
the actor model . It supports all the necessary technologies, like WebSockets, TLS and
HTTP / 2.0 . This framework supports various handlers and resources, but in our application only a couple of main routes were used:
/ws is the main resource for WebSocket communications./ - the main handler that gives access to the static frontend application.
By default,
actix-web runs workflows in an amount corresponding to the number of processor cores available on the local computer. This means that if an application has a state, it will have to be safely shared between all threads, but, thanks to Rust's robust parallel computing patterns, this is not a problem. Whatever the case, the backend should be a stateless system, since many of its copies can be deployed in parallel in a cloudy environment (like
Kubernetes ). As a result, the data that forms the state of the application should be separated from the backend. For example, they may reside within a separate instance of a
Docker container.
PostgreSQL DBMS and Diesel ProjectI decided to use
PostgreSQL as the main data storage. Why? This choice determined the existence of the remarkable
Diesel project, which already supports PostgreSQL and offers a safe and extensible ORM system and query building tool for it. All this fits perfectly with the needs of our project, since
actix-web already supports Diesel. As a result, here, to perform CRUD operations with session information in the database, you can use a special language that takes into account the specifics of Rust. Here is an example of an
UpdateSession handler for
actix-web based on Diesel.rs.
impl Handler<UpdateSession> for DatabaseExecutor { type Result = Result<Session, Error>; fn handle(&mut self, msg: UpdateSession, _: &mut Self::Context) -> Self::Result {
To establish a connection between
actix-web and Diesel, use the
r2d2 project. This means that we have (in addition to the application with its workflows) a shared application state that supports multiple connections to the database as a single pool of connections. This greatly simplifies the serious scaling of the backend, makes this solution flexible.
Here you can find the code responsible for creating the server instance.
Backend testing
Integration testing of the backend in our project is performed by running a test server instance and connecting to an already running database. Then you can use the standard WebSocket client (I used
tungstenite ) to send data to the server, taking into account the features of the Cap'n Proto protocol, and to compare the results with the expected ones. This testing scheme has performed well. I did not use special
actix-web test servers , since much more work is not required to set up and run a real server. The unit testing of the backend turned out to be, as expected, quite simple, there were no special problems with conducting such tests.
Project rollout
The application is very easy to deploy using the Docker image.
DockerUsing the
make deploy you can create an image called
webapp that contains statically related executable backend files, the current
Config.toml file, TLS certificates, and static frontend content. The build of fully statically related executable files in Rust is implemented using a modified Docker image of the
rust-musl builder . A complete web application can be tested by using the
make run command, which launches a container with network support. The PostgreSQL container, for the system to work, must be launched in parallel with the application container. In general, the process of deploying our system is quite simple; in addition, thanks to the technologies used here, we can talk about its sufficient flexibility, which simplifies its possible adaptation to the needs of an evolving application.
Technologies used in project development
Here is the application dependency diagram.
Technologies used in the development of a web application on RustThe only component used by both the frontend and the backend is the Rust version of Cap'n Proto, which requires the locally installed Cap'n Proto compiler to create.
Results Is Rust ready for web production?
This is a big question. Here is what I can answer for him. From the server's point of view, I tend to answer “yes”, since the Rust ecosystem, in addition to
actix-web , has a very mature
HTTP stack and many different
frameworks for the rapid development of server APIs and services.
If we talk about the frontend, then here, thanks to the universal attention to the WebAssembly, now there is a huge job. However, projects created in this area must reach the same maturity that server projects have achieved. This is particularly true for API stability and testing capabilities. So now I say no to using Rust in the frontend, but I cannot help but note that it is moving in the right direction.
Dear readers! Do you use Rust in web development?