How to route and return in actix_web
When trying to learn actix_web, you might be intemidated by the number of different ways to setup a given route. The documentation uses them seemingly interchangably, and you might wonder if you're doing things the "correct" way while writing your backend code. The goal of this blog post is to try and explain the differences between these diffferent methods, and allow you to make a more educated decision.
Types of Routing
There are two main ways to route incoming requests with actix_web.
- Register a Route
- Register a Service
On the surface level, route is the much easier option. You specify a path, the type of request, and the function to route to. With service, you are registering a type that implements HttpServiceFactory.
Looking at the code snippit above, it's easy to see which of these is more desirable to route a basic request to a basic function. But service registration was a trick up it's sleeve.
Actix attributes
Let's take a look at an example. We're going to have two identical functions that both return "Hello World", with the only difference being one will be tagged with actix_web's GET attribute
.
Now when we look at the difference between the two, the service is much more concise, and it is very clear from looking at the function definition, what endpoint is being handled. Let's use a tool called cargo-expand to see what is generated by the compiler when I add the attribute tag above hello_service
.
We can see that the attribute generated a unit struct named after our function, then implemented HttpServiceFactory on that struct for us. Now, when we pass hello_service
into the service method, we have a type that implements HttpServiceFactory, and still contains the function block we defined.
Drawbacks to Attributes
One drawback with using this method is that our original function is now practically inaccessable. Another drawback is the lack of flexibility in the implementing of our resource. For most simple routes, this should probably be your main go-to. But as the complexity rises, you might find that you need to manually register resources.
tl;dr - For basic routes, use a combination of the attributes
actix provides for HTTP methods in combination with .service()
unless you need to call the function you're routing to from anywhere else. Once things become more complicated, you can use manual service creation to get more granular controll over your routing.
Return Types
There are many different ways to return from routes in actix_web. These were a big source of confusion for me at the start so I want to demystify them to the best of my abilities. The Responder trait in actix_web is used to turn a return value into an HttpReponse. Responder is implemented for a bunch of types by default. Above we had an example of a function that returned a 'static str
, actix_web can turn this type into an HttpReponse for us, saving us a bit of time and allowing our code to be reused elsewhere.
Here is a quick cheat sheet to reference:
impl Responder | Use to avoid manually constructing HttpResponses | Most General Purpose. |
actix_web::Result | Use when your error types are friendly, or if you're using your own custom errors. | Use in combination with impl Responder |
The raw type | Use to serve static information, and when you want to reuse your function outside of a purly actix context. | Use with string types that have built-in Responder implementations. Or custom types that you implement Responder for. |
Where this becomes more challenging is with our own custom types. Thankfully actix_web provides some very useful tools to make this process simple for most of what you might want to respond with.
Here, we have a type A
that implements Serde::Serialize. We can use impl Responder
as our opaque return type, as our struct can be serialized, and Responder is implemented for us for Json<T>
where T
implements Serialize
.
We can also take advantage of actix's Result to allow us to return errors more easily. I personally wouldn't use this method if you're not going to create your own errors for your project.
Here is an example of a custom error being used with actix_web's Result. The implementation example is down below.
We can also decide to implement Responder manually for our type, then return that type and have the HttpResponse be created for us. This can work well in combination with actix's Result. This results in the most Rust looking code to my eyes, but if you have a lot of different types of data to return, implementing Responder manually for them is likely not worth the time, or the added clutter of your codebase.
Obviously, you can construct your own HttpReponse as well. This method requires a lot of boiler-plate code when it comes to handling Error cases. As your project grows, you will probably want to avoid this method.
Summary
I hope this blog was helpful if you're looking into starting an actix_web project. If I made any bone-headed mistakes, feel free to send me an email @ DanielBatesJ@gmail.com.
The code examples are up on my GitHub.
Comments