Expand description
§Creating task handlers
This tutorial covers the basics of writing task handlers,
and implementing custom argument extraction via FromRequest.
§Introduction
The first argument of any task function is the type Args which is tied to the backend’s task type.
Eg if you are writing a task for an email service, Args might be a struct Email that includes fields like user_id, subject, and message.
A rule of thumb is to never store database models as task arguments.
Instead of doing this:
struct User {
id: String,
// other fields...
}
struct Email {
user: User,
subject: String,
message: String,
}Do this:
struct Email {
user_id: String,
subject: String,
message: String,
}All the primitive types (e.g. String, u32) can be used directly as task arguments.
Note:
Some backends like
apalis-cronoffer a specificArgstype (Tick) for cron jobs while most others likepostgresuse a more genericArgstype.
A guide for extracting complex information from tasks using FromRequest is available in step 3.
§1. Writing basic task handlers
Task handlers are async functions that process a task. You can use the task_fn helper
to wrap your handler into a service.
#[derive(Clone)]
struct State;
// A simple handler that takes an id and injected state
async fn handler(id: u32, state: Data<State>) -> String {
format!("Got id {} with state", id)
}You would need to inject the state in your worker builder:
let worker = WorkerBuilder::new()
.backend(in_memory)
.data(State)
.build(handler);§2. Dependency Injection in handlers
apalis-core supports default injection for common types in your handler arguments, such as:
Attempt: Information about the current attemptWorkerContext: Worker contextData<T>: Injected data/stateTaskId: The unique ID of the task
Example:
#[derive(Clone)]
struct State;
async fn process_task(_: u32, attempt: Attempt, state: Data<State>, id: TaskId) -> String {
format!("Attempt {} for task {} with state", attempt.current(), id)
}§3. Implementing custom argument extraction with FromRequest
You can extract custom types from the request by implementing FromRequest.
Suppose you have a task to send emails, and you want to automatically extract a User from the task’s user_id:
struct Email {
user_id: String,
subject: String,
message: String,
}
// Implement FromRequest for User
impl<Ctx: Sync> FromRequest<Task<Email, Ctx>> for User {
type Error = BoxDynError;
async fn from_request(req: &Task<Email, Ctx>) -> Result<Self, Self::Error> {
let user_id = req.args.user_id.clone();
// Simulate fetching user from DB
Ok(User { id: user_id })
}
}
// Now your handler can take User directly
async fn send_email(email: Email, user: User) -> Result<(), BoxDynError> {
// Use email and user
Ok(())
}§4. How It Works
task_fnwraps your handler into aTaskFnservice.- Arguments are extracted using
FromRequest. - DI types are injected automatically.
- The handler’s output is converted to a response using
IntoResponse.