{"id":14986478,"url":"https://github.com/nwrenger/gluer","last_synced_at":"2026-05-09T06:02:11.460Z","repository":{"id":250514563,"uuid":"834678414","full_name":"nwrenger/gluer","owner":"nwrenger","description":"A wrapper for Rust frameworks that eliminates redundant type and function definitions between the frontend and backend","archived":false,"fork":false,"pushed_at":"2026-02-25T22:39:13.000Z","size":197,"stargazers_count":4,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-03-23T18:19:15.787Z","etag":null,"topics":["axum","proc-macro","rust","typescript","web"],"latest_commit_sha":null,"homepage":"https://crates.io/crates/gluer","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nwrenger.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"nwrenger"}},"created_at":"2024-07-28T02:54:52.000Z","updated_at":"2026-02-25T22:39:17.000Z","dependencies_parsed_at":"2025-02-06T16:32:50.207Z","dependency_job_id":"fdb16a5a-ba84-40e5-9317-ece96fe7f7be","html_url":"https://github.com/nwrenger/gluer","commit_stats":{"total_commits":103,"total_committers":1,"mean_commits":103.0,"dds":0.0,"last_synced_commit":"c04a2ff05c865527e0a5c51123189ec0fd429e03"},"previous_names":["nwrenger/gluer"],"tags_count":22,"template":false,"template_full_name":null,"purl":"pkg:github/nwrenger/gluer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nwrenger%2Fgluer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nwrenger%2Fgluer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nwrenger%2Fgluer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nwrenger%2Fgluer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nwrenger","download_url":"https://codeload.github.com/nwrenger/gluer/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nwrenger%2Fgluer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32806818,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-08T08:22:46.396Z","status":"online","status_checked_at":"2026-05-09T02:00:06.633Z","response_time":123,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["axum","proc-macro","rust","typescript","web"],"created_at":"2024-09-24T14:12:56.063Z","updated_at":"2026-05-09T06:02:11.450Z","avatar_url":"https://github.com/nwrenger.png","language":"Rust","funding_links":["https://github.com/sponsors/nwrenger"],"categories":[],"sub_categories":[],"readme":"# gluer\n\n[![crates.io](https://img.shields.io/crates/v/gluer.svg)](https://crates.io/crates/gluer)\n[![crates.io](https://img.shields.io/crates/d/gluer.svg)](https://crates.io/crates/gluer)\n[![docs.rs](https://docs.rs/gluer/badge.svg)](https://docs.rs/gluer)\n\nA wrapper for Rust frameworks that eliminates redundant type and function definitions between the frontend and backend. Currently, it supports the `axum` and `actix` frameworks.\n\n## Origin of the Name\n\nThe name \"gluer\" is inspired by the tool's primary function, **gluing together different parts of a Rust-based web application**. Just as glue binds different materials together to form a cohesive whole, `gluer` integrates various components of the frontend and backend, ensuring they work seamlessly without redundant code.\n\n## Installation\n\nAdd this to your `Cargo.toml`:\n\n```toml\n[dependencies]\ngluer = \"0.9.1\" # specify the framework you want to use, e.g. none for axum and `features = [\"actix\"], default-features = false` for actix\n```\n\n## Features\n\n- Define routing and API generation as outlined in [How to use](#how-to-use).\n- Everything is done on macro expansion (compile time), even the generating of the JavaScript/TypeScript file.\n- Support `axum`'s and `actix`'s types completely.\n- Generate a JavaScript file with:\n  - A custom prefix for the URL of the request\n  - Functions to access the api, infers input and output parameters for that\n  - Enums as the JavaScript equivalent\n  - Supports converting docstring to the JavaScript equivalent, even of fields of enums\n  - Tuples as the JavaScript equivalent, also supports tuples in `axum`'s and `actix`'s path\n  - No extra dependencies\n- Generate a TypeScript file with:\n  - A custom prefix for the URL of the request\n  - Functions to access the api, infers input and output types for that\n  - Structs as Interfaces, supports changing the generated type via the `#[meta(...)]` attribute to be skipped, a different rust type or an optional (the `?` in TypeScript)\n  - Enums as the TypeScript equivalent, enums with values are not supported, because of the lack of that feature in TypeScript\n  - Types as the TypeScript equivalent\n  - Supports converting docstring to the TypeScript equivalent, even of fields of structs and enums\n  - Tuples as the TypeScript equivalent, also supports tuples in `axum`'s path\n  - Supports converting rust specific types as `Result` as custom ones using the `custom = [Type, *]` attribute\n  - Generics, even multiple and nested ones, look for that [here](#complete-examples)\n  - No extra dependencies\n\n## How to use\n\n`gluer` generates an api endpoint `.js`/`.ts` file. To use it, follow these steps:\n\n### Step 1: Define Structs and Functions\n\nTo define your structs, functions and enums, use the `#[metadata]` macro along with the `#[meta(...)] `attribute. This enables the `generate!` macro to find those and converting them into the JavaScript/TypeScript equivalent.\n\n```rust\nuse axum::{\n    Json,\n    extract::Path,\n};\nuse gluer::metadata;\nuse serde::{Serialize, Deserialize};\n\n/// Define a struct with the metadata macro\n/// Note: This is a docstring and will be\n/// converted to the TypeScript equivalent\n#[metadata(custom = [Result])]\n#[derive(Serialize, Deserialize)]\nstruct Book {\n    /// This will also be converted to a docstring\n    name: String,\n    // When you use types as `Result`, `Option` or `Vec` the\n    // macro sees them as a default rust type, meaning when\n    // you wanting to use custom ones you have to specify that\n    // via the `custom` attribute on `#[metadata]`\n    some_result: Result\u003cString\u003e,\n    // Sometimes you don't have access to certain data types,\n    // so you can override them using `#[meta(into = Type)]`\n    // or skip them entirely via `#[meta(skip)]`\n    // or set them to be optional via `#[meta(optional)]`\n    // (-\u003e an interface field with a `?`)\n    #[meta(into = String)]\n    user: User,\n    #[meta(skip)]\n    borrower: User,\n    #[meta(optional)]\n    reservation: String,\n}\n\n// Everything you want to use, even if it's just a\n// dependency of struct or type, needs to be declared\n// with the `#[metadata]` macro\n#[metadata]\ntype Result\u003cT\u003e = std::result::Result\u003cT, String\u003e;\n\n#[derive(Default, Serialize, Deserialize)]\nstruct User {\n    name: String,\n    password: String,\n}\n\n// Define an enum with the metadata macro\n// Note: Enums with values are not supported\n#[metadata]\n#[derive(Default, Serialize, Deserialize)]\nenum BookState {\n    #[default]\n    None,\n    Reserved,\n    Borrowed,\n}\n\n// Define the functions with the metadata macro\n#[metadata]\nasync fn root() -\u003e Json\u003cString\u003e {\n    \"Hello, World!\".to_string().into()\n}\n\n// Supports axum's and actix's input types\n#[metadata]\nasync fn book(Json(b): Json\u003cBook\u003e) -\u003e Json\u003cBook\u003e {\n    Json(b)\n}\n\n// Also tuples in paths\n#[metadata]\nasync fn path(Path(p): Path\u003c(String, String)\u003e) -\u003e Json\u003c(String, String)\u003e {\n    p.into()\n}\n\n// Supports enums\n#[metadata]\nasync fn book_state() -\u003e Json\u003cBookState\u003e {\n    BookState::default().into()\n}\n```\n\n### Step 2: Add Routes \u0026\u0026 Generate API\n\nUse the `generate!` macro to define your router and other telemetry to generate the API. You have to define the `output` location of the JavaScript/TypeScript file and the `routes`. The language of the output file is determined by the file extension (`.js` or `.ts`). Note that inline functions cannot be used in the `router` field because the function names in the generated JavaScript/TypeScript file are inferred from the handler function names.\n\n```rust, ignore\nuse axum::{\n    routing::get,\n    Json,\n    Router,\n    extract::Path,\n};\nuse gluer::{generate, metadata};\n\n// without `#[metadata]`, it's non-API-important\nasync fn root() -\u003e String {\n    \"Hello, World!\".to_string()\n}\n\n// done like above\n#[metadata]\nasync fn hello(Path(h): Path\u003cString\u003e) -\u003e Json\u003cString\u003e {\n    h.into()\n}\n\nlet mut app: Router\u003c()\u003e = generate! {\n    routes = {\n        // Add API-important inside the routes field\n        \"hello\" = get(hello),\n    },\n    output = \"tests/api.ts\",\n}\n// Add non-API-important outside the macro\n.route(\"/\", get(root));\n```\n\n### Additional Notes\n\nThe `generate!` macro includes several optional fields that you can customize to fit your needs. One of these is `prefix`, which allows you to set a URL prefix for your API routes. By default, this is an empty string (`\"\"`), but you can change it to something like `\"/api\"`. Be aware that the prefix should not end with a `/`.\n\nAnother customizable option is `files`, which defines the Rust project files that contain the source code for handler functions and dependencies. This can be a single string literal (e.g., `\"src\"`) or an array of string literals (e.g., `[\"src/db\", \"src\", \"src/error.rs\"]`). These paths are used to extract type information for the TypeScript client. The default value is `\"src\"`, which should be suitable in most scenarios.\n\nFinally, once the router is generated by the macro, along with the API, you can immediately use it to start your server or perform other operations.\n\n## Complete Examples\n\nBelow is a complete example demonstrating the use of `gluer` with `axum`. [Here](/tests/api_axum.ts) is the generated output. You can find the equivalent for `actix` [here](/tests/actix.rs) and explore [this bigger project](https://github.com/wrenger/schiller-lib) which uses `gluer`.\n\n```rust\nuse axum::{\n    extract::{Path, Query},\n    routing::get,\n    Json, Router,\n};\nuse gluer::{generate, metadata};\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\n\n// An example of a simple function with a `Path` and a `Query` extractor\n#[metadata]\nasync fn fetch_root(Query(test): Query\u003cHashMap\u003cString, String\u003e\u003e, Path(p): Path\u003cusize\u003e) -\u003e String {\n    test.get(\u0026p.to_string()).unwrap().clone()\n}\n\n#[metadata]\n#[derive(Deserialize)]\nstruct QueryOptions {\n    id: usize,\n    query: String,\n}\n\n// And one with a `Query` extractor using a struct\n#[metadata]\nasync fn fetch_other(Query(test): Query\u003cQueryOptions\u003e) -\u003e String {\n    format!(\"{}: {}\", test.id, test.query)\n}\n\n// Generics are supported, multiple even\n// Note: This is not a docstring and won't\n// be converted\n#[metadata]\n#[derive(Serialize, Deserialize, Default)]\npub struct Hello\u003cT: Serialize, S\u003e {\n    name: S,\n    vec: Vec\u003cT\u003e,\n}\n\n/// Might want to look into the `api_axum.ts` file to see the docstring for this struct\n#[metadata]\n#[derive(Serialize, Deserialize, Default)]\nstruct Age {\n    /// Even supports docstring on fields and optional fields\n    #[meta(optional)]\n    age: AgeInner,\n}\n\n#[metadata]\n#[derive(Serialize, Deserialize, Default)]\nstruct AgeInner {\n    // Thats quite big and also supported\n    /// This gets converted to a `string` on the TypeScript side\n    /// because `numbers` there cannot be greater than 64 bits\n    age: u128,\n}\n\n#[metadata]\n#[derive(Serialize, Deserialize, Default)]\nstruct Huh\u003cT\u003e {\n    huh: T,\n}\n\n// Even deep nested generics are supported and tagging default rust types as Custom\n/// Docstrings for functions are also supported\n#[metadata(custom = [Result])]\nasync fn add_root(\n    Path(_): Path\u003cusize\u003e,\n    Json(hello): Json\u003cResult\u003cHello\u003cHello\u003cHuh\u003cHuh\u003cHello\u003cAge, String\u003e\u003e\u003e, String\u003e, String\u003e\u003e\u003e,\n) -\u003e Json\u003cResult\u003cString\u003e\u003e {\n    Json(Ok(hello.unwrap().name.to_string()))\n}\n\n#[metadata]\n#[derive(Serialize, Deserialize)]\nenum Alphabet {\n    A,\n    B,\n    C,\n    // etc\n}\n\n// Even tuples are supported\n#[metadata]\nasync fn get_alphabet(Path(r): Path\u003c(Alphabet, S)\u003e) -\u003e Json\u003c(Alphabet, S)\u003e {\n    Json(r)\n}\n\n/// An example how an api error type could look like\n#[metadata]\n#[derive(Serialize, Deserialize, Debug)]\nenum Error {\n    /// Normal 404 error\n    NotFound,\n    /// Internally something really bad happened\n    InternalServerError,\n}\n\n// And types?!?\n#[metadata]\ntype Result\u003cT\u003e = std::result::Result\u003cT, Error\u003e;\n\n#[metadata]\ntype S = String;\n\n#[tokio::test]\nasync fn main_test() {\n    let _app: Router\u003c()\u003e = generate! {\n        routes = { // required\n            \"/{p}\" = get(fetch_root).post(add_root),\n            \"/char/{path}/metadata/{path}\" = get(get_alphabet),\n            \"/other\" = get(fetch_other),\n        },\n        files = \"tests\", // Make sure to remove this when copying this example into a normal project\n        output = \"tests/api_axum.ts\", // required\n    };\n\n    let _listener = tokio::net::TcpListener::bind(\"127.0.0.1:8080\")\n        .await\n        .unwrap();\n    // starts the server, comment in and rename `_app` and `_listener` to run it\n    // axum::serve(listener, app).await.unwrap();\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnwrenger%2Fgluer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnwrenger%2Fgluer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnwrenger%2Fgluer/lists"}