# rjapi - JSON:API 1.1 Implementation for Rust
[](https://crates.io/crates/rjapi)
[](https://docs.rs/rjapi)
[](https://github.com/mohdbk/rjapi/blob/master/LICENSE)
A framework-agnostic, ergonomic, and type-safe implementation of the [JSON:API 1.1 specification](https://jsonapi.org/) for Rust applications.
## Features
- ✅ **Compliant & Modern:** Fully compliant with the JSON:API 1.1 specification.
- 🚀 **Framework-Agnostic:** Designed to work seamlessly with any web framework, including Axum, Actix Web, and Rocket.
- 🛡️ **Type-Safe by Design:** Leverages Rust's type system to prevent common errors at compile time. Application-specific models are used directly, reducing the need for manual and error-prone conversions.
- ✨ **Ergonomic API:** A fluent, builder-based API for constructing responses and a powerful extractor for handling requests makes your code cleaner and more readable.
- 📦 **Lightweight & Efficient:** A minimal dependency footprint and an architecture focused on performance ensure your application remains fast.
- 🔧 **Extensible:** Easily extendable to fit your application's specific needs.
- 📚 **Comprehensive Documentation:** Clear and detailed documentation with practical examples.
## Installation
Add this to your `Cargo.toml`:
```toml
[dependencies]
rjapi = "0.0.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
```
## Usage
### Defining a Resource
To make your models compatible with `rjapi`, implement the `JsonApiResource` trait.
```rust
use rjapi::JsonApiResource;
use serde::Serialize;
use serde_json::json;
#[derive(Serialize)]
struct Post {
id: String,
title: String,
content: String,
}
impl JsonApiResource for Post {
const RESOURCE_TYPE: &'static str = "posts";
fn id(&self) -> String {
self.id.clone()
}
fn attributes(&self) -> serde_json::Value {
json!({
"title": self.title,
"content": self.content,
})
}
}
```
### Creating a Response
Use the `JsonApiResponse` builder to construct type-safe, compliant JSON:API responses.
```rust
use rjapi::{JsonApiResponse, Resource};
# use rjapi::JsonApiResource;
# use serde::Serialize;
# use serde_json::json;
# #[derive(Serialize)]
# struct Post {
# id: String,
# title: String,
# content: String,
# }
# impl JsonApiResource for Post {
# const RESOURCE_TYPE: &'static str = "posts";
# fn id(&self) -> String { self.id.clone() }
# fn attributes(&self) -> serde_json::Value { json!({ "title": self.title, "content": self.content }) }
# }
let post = Post {
id: "1".to_string(),
title: "Hello, World!".to_string(),
content: "This is the first post.".to_string(),
};
let resource = Resource {
resource_type: Post::RESOURCE_TYPE.to_string(),
id: post.id(),
attributes: Some(post.attributes()),
relationships: None,
links: None,
meta: None,
};
let response = JsonApiResponse::new(resource).build();
println!("{}", serde_json::to_string_pretty(&response).unwrap());
```
### Framework Integration (Axum Example)
The `JsonApiRequest` extractor simplifies request handling in your web framework.
```rust
use axum::{
extract::State,
http::StatusCode,
response::{IntoResponse, Response},
Json, Router,
};
use rjapi::{JsonApiResponse, JsonApiResource, Resource, JsonApiRequest};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;
// Application state
#[derive(Clone)]
struct AppState {
posts: Arc<RwLock<Vec<Post>>>,
}
# #[derive(Clone, Serialize, Deserialize)]
# struct Post {
# id: String,
# title: String,
# content: String,
# }
# impl JsonApiResource for Post {
# const RESOURCE_TYPE: &'static str = "posts";
# fn id(&self) -> String { self.id.clone() }
# fn attributes(&self) -> serde_json::Value { serde_json::json!({ "title": self.title, "content": self.content }) }
# }
// A specific type for post creation attributes
#[derive(Deserialize)]
struct PostAttributes {
title: String,
content: String,
}
// Axum handler for creating a post
async fn create_post(
State(state): State<AppState>,
Json(request): Json<JsonApiRequest<PostAttributes>>,
) -> impl IntoResponse {
let attributes = request.data.attributes;
let post = Post {
id: uuid::Uuid::new_v4().to_string(),
title: attributes.title,
content: attributes.content,
};
state.posts.write().await.push(post.clone());
let resource = Resource {
resource_type: Post::RESOURCE_TYPE.to_string(),
id: post.id(),
attributes: Some(serde_json::to_value(post).unwrap()),
..Default::default()
};
let response = JsonApiResponse::new(resource).build();
(StatusCode::CREATED, Json(response))
}
```
## Documentation
For full documentation, visit [docs.rs/rjapi](https://docs.rs/rjapi).
## Examples
Check out the [examples](examples/) directory for more detailed usage examples:
- [Basic Usage](examples/basic.rs)
- [Axum Integration](examples/axum.rs)
- [Error Handling](examples/error_handling.rs)
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## Contributing
Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details on how to submit pull requests, report issues, and suggest improvements.
## Changelog
### 0.0.1 (Initial Release)
- Framework-agnostic JSON:API 1.1 implementation
- Support for resources, relationships, errors, and metadata
- Middleware for content-type validation
- Comprehensive examples and documentation