\usepackage{rust}
Compile LaTeX to PDF from Rust using the FormaTeX REST API. Async reqwest + tokio — no TeX Live installation, no binary to bundle.
\section{Why Rust}
reqwest's async client maps naturally onto Tokio's runtime. Compile multiple documents concurrently with no extra complexity.
Your Rust binary stays small — no TeX Live, no apt install texlive-full. FormaTeX handles the compilation environment entirely.
Rust's Result type pairs well with the FormaTeX API. Map HTTP errors to domain errors at the type level — no runtime surprises.
\section{Dependencies}
[dependencies] reqwest = { version = "0.12", features = ["json"] } tokio = { version = "1", features = ["full"] } serde_json = "1" serde = { version = "1", features = ["derive"] } # For Axum integration axum = "0.7" bytes = "1"
\section{Quick Start}
The example below compiles a minimal LaTeX document using reqwest and writes the resulting PDF to disk. Read your API key from the environment with std::env::var("FORMATEX_API_KEY").
use reqwest::Client;
use serde_json::json;
use std::fs;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api_key = std::env::var("FORMATEX_API_KEY")?;
let latex = r#"\documentclass{article}
\usepackage{amsmath}
\begin{document}
Hello from Rust! $E = mc^2$
\end{document}"#;
let client = Client::new();
let response = client
.post("https://api.formatex.io/v1/compile/sync")
.bearer_auth(&api_key)
.json(&json!({ "source": latex, "engine": "pdflatex" }))
.send()
.await?;
if !response.status().is_success() {
let body = response.text().await?;
return Err(format!("API error: {body}").into());
}
let pdf = response.bytes().await?;
fs::write("output.pdf", &pdf)?;
println!("PDF saved to output.pdf");
Ok(())
}\section{Axum Handler}
Register a shared AppState with your Axum router and mount compile_handler at any route. The handler accepts a JSON body with source and optional engine, then streams the PDF bytes back directly to the client.
use axum::{
body::Bytes,
extract::State,
http::{HeaderMap, StatusCode},
response::{IntoResponse, Response},
Json,
};
use reqwest::Client;
use serde::Deserialize;
#[derive(Clone)]
pub struct AppState {
pub http: Client,
pub api_key: String,
}
#[derive(Deserialize)]
pub struct CompileRequest {
pub source: String,
#[serde(default = "default_engine")]
pub engine: String,
}
fn default_engine() -> String {
"pdflatex".to_string()
}
pub async fn compile_handler(
State(state): State<AppState>,
Json(body): Json<CompileRequest>,
) -> Result<Response, (StatusCode, String)> {
let res = state
.http
.post("https://api.formatex.io/v1/compile/sync")
.bearer_auth(&state.api_key)
.json(&serde_json::json!({
"source": body.source,
"engine": body.engine,
}))
.send()
.await
.map_err(|e| (StatusCode::BAD_GATEWAY, e.to_string()))?;
if !res.status().is_success() {
let msg = res.text().await.unwrap_or_default();
return Err((StatusCode::UNPROCESSABLE_ENTITY, msg));
}
let pdf: Bytes = res
.bytes()
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
let mut headers = HeaderMap::new();
headers.insert("Content-Type", "application/pdf".parse().unwrap());
headers.insert(
"Content-Disposition",
"attachment; filename=output.pdf".parse().unwrap(),
);
Ok((headers, pdf).into_response())
}\section{Authentication}
Store your API key in the environment — never hardcode it in source files. Read it at startup and fail fast if it is missing:
// Fail fast at startup let api_key = std::env::var("FORMATEX_API_KEY").expect("FORMATEX_API_KEY not set");
.env file with the dotenvy crate during local development.Authorization: Bearer <key>.\section{Reference}
| Field | Type | Required | Description |
|---|---|---|---|
| source | string | Yes | Full LaTeX source code as a string |
| engine | string | No | Engine to use. Default: "pdflatex". Options: pdflatex, xelatex, lualatex, latexmk |
POST https://api.formatex.io/v1/compile/sync — Returns raw PDF bytes on success (200), or JSON error on failure (422/401).
\section{Error Handling}
Check response.status().is_success() before reading the body. Map HTTP errors to your own error type for clean propagation:
200 — raw PDF bytes. Read with response.bytes().await?.422 — LaTeX compilation error. JSON body contains an error field with the TeX log excerpt.401 — invalid or missing API key.\end{rust}
Get your API key and have PDF output from Rust in minutes — no TeX Live required.
One quick thing
We track anonymous usage — page views, feature usage, compilation events — to understand what works and what doesn't. No ads, no personal data, no third-party sharing.