3 unstable releases
0.2.0 | Sep 19, 2024 |
---|---|
0.1.1 | Jun 13, 2024 |
0.1.0 | Jun 13, 2024 |
#1789 in Web programming
105 downloads per month
59KB
63 lines
Tailwag - The Rapid Rust Toolkit
This repo consolidates a variety of Rust libraries intended to rapidly create REST APIs and data applications. The end goal of Tailwag is to enable same-day zero-to-production development and deployments for new services, with sensible defaults.
The current developmental version supports standing up a REST service and a Postgres datastore for datatypes composed of primitives, timestamps, and UUIDs. It does currently support joins or nested data structures, except for limited One-to-one data structure support.
Project Status: Experimental
This project is being actively developed in my free time, although it is still
in early stages. It currently has a large number of unwrap()
calls, TODO
comments, and unoptimized code. I've made an effort to document known technical
debt as comments in the code. The macros in particular are a bit funky (see
FAQ - "What's up with the macros?").
I've made made some recent improvements to the error handling and hope soon to
eliminate all .unwrap()
calls, which currently cause unexpected crashes in
some cases.
It is also currently single-threaded, only able to handle one request at a time, which is due to the WIP status of the underlying HTTP implementation.
Getting Started
Tailwag's most useful feature right now is in standing up a REST API for a set
of defined data types. The following code example will stand up an API with full
CRUD operations for the Item
struct:
NOTE In order to run the example, you will need to have a Postgres instance running locally. If no environment config is found, Tailwag will use
postgres:postgres
as the username:password in development mode.docker run --name postgres-dev -e POSTGRES_PASSWORD=postgres -e POSTGRES_USER=postgres -p 5432:5432 postgres
Minimal Working Example
The following is an entire Rust program build on Tailwag. This chunk of code
creates a REST webservice running on http://localhost:8081
, with the /todo/
endpoint supporting CRUD operations through POST
, GET
, PATCH
, and DELETE
HTTP methods. /todo/{id}
is also supported for GET.
Note that tailwag has some dependencies that are not current re-exported by tailwag. Let's create a project from scratch to get our first Tailwag application:
First, create a new Cargo project:
cargo new my_tailwag_app
cd my_tailwag_app
Next add our dependencies:
[!TIP] The crates.io crate is not updated frequently. To stay up-to-date with this crate, consider using
cargo add --git https://github.com/nikwithak/tailwag
.
cargo add tailwag
cargo add serde --features derive # Required for just about all tailwag operations.
cargo add chrono # If you need datetime support.
cargo add uuid # Required for tailwag database support.
cargo add sqlx # Required for database communication with Postgres.
cargo add tokio --features full # Required for the async runtime.
Now our application:
use tailwag::macros::derive_magic;
use tailwag::web::application::WebService;
#[tokio::main]
async fn main() {
derive_magic! {
struct Todo {
id: uuid::Uuid, // Note: Currently all data types MUST have an `id` of type `uuid::Uuid`. A future version will remove this limitation.
title: String,
description: String,
due_date: chrono::NaiveDateTime,
}
}
WebService::builder("Todo Service")
.with_resource::<Todo>()
.build_service()
.run()
.await
.expect("Web service crashed.");
}
The `derive_magic!` macro derives a lot of traits that the `WebService` struct
uses for building routes, postgres data, etc.
Don't forget to start your postgres instance:
docker run --name some-postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_USER=postgres -p 5432:5432 postgres
Now run the service:
cargo run
Hit the endpoint with some TODOs:
curl -X POST http://localhost:8081/todo \
-H "Content-Type: application/json" \
-d '{"title": "Add multithread support", "description": "To be viable in a Production environment, tailwag needs to support multiple concurrent requests.", "due_date": "2024-06-13T00:00:00"}'
curl -X POST http://localhost:8081/todo \
-H "Content-Type: application/json" \
-d '{"title": "Remove id requirement", "description": "Update tailwag to allow more flexibility for table primary keys.", "due_date": "2024-06-13T00:00:00"}'
Now let's GET all the TODOs:
curl http://localhost:8081/todo | jq
[
{
"id": "68e20f9e-0fbd-4bda-9095-b2749948579d",
"title": "Add multithread support",
"description": "To be viable in a Production environment, tailwag needs to support multiple concurrent requests.",
"due_date": "2024-06-13T00:00:00"
},
{
"id": "505180d4-eb25-4352-8599-6e4d69bd2806",
"title": "Remove id requirement",
"description": "Update tailwag to allow more flexibility for table primary keys.",
"due_date": "2024-06-13T00:00:00"
}
]
Or just a single TODO:
curl http://localhost:8081/todo | jq
{
"id": "68e20f9e-0fbd-4bda-9095-b2749948579d",
"title": "Add multithread support",
"description": "To be viable in a Production environment, tailwag needs to support multiple concurrent requests.",
"due_date": "2024-06-13T00:00:00"
}
This and more detailed examples can be found in the /examples
folder. There
are also some more detailed examples in the
tailwag_web_service
crate.
How does it work?
Tailwag is built on a few other crates buitl for this project.
-
https://github.com/nikwithak/tailwag - This crate! You're already here for the README! This is the main crate, but primarily it just re-exports the bigger logic crates to house the documentation.
-
https://github.com/nikwithak/tailwag-web-service - This is a lot of the core logic for building the web service application, and my implementation of the HTTP spec.
-
https://github.com/nikwithak/tailwag_orm - This crate houses the ORM - data definitions and manager/provider structs for the data objects. Also houses a lot of the ORM-related macros.
-
https://github.com/nikwithak/tailwag_utils - This is just a handful of small utilities. Nothing fancy.
-
https://github.com/nikwithak/tailwag_forms - This is a smaller crate for exporting the data definitions into a JSON spec, that can be parsed by other applications (in this case formiliar - see below).
-
https://github.com/nikwithak/formiliar - The react library for generating forms. This is an older version, but the idea is that tailwag_forms exports a JSON file that can be read in and parsed with this library. Coming soon: This library was adapted into a web management form auto-generator, that integrates with Tailwag by reading in the schema generated by
tailwag_forms
.
How can I support the project?
-
Use it! - Please try out the API, build some projects with it, and put it to the real test. Please file issues for any challenges or bugs you encounter. If you have a use case that isn't supported, please file it as an issue.
-
Stars & Likes - Consider starring or watching the project, and/or following me (@nikwithak) on GitHub. These are zero-cost ways to extend the audience of my work.
-
Donate or Sponsor - This project has been written entirely in my free time, and your donations allow me to continue devoting time and energy to maintaining it and developing new features. All donation amounts are accepted and appreciated. I would be thrilled to be able to focus on this project full-time, and your donations help achieve that goal.
If you would like to sponsor this project, please email nik@nikgilmore.com, I would love to chat!
Tenets [Opinionated Framework]
Tailwag is an opinionated framework, and will usher you into specific patterns. I plan to shortly publish The Tailwag Book, a free and open source user guide to building applications with Tailwag.
Tailwag focuses on the following tenets as the building blocks for your basic application:
-
Define Your Data
- The data model is the most important part of an application, and therefore it's essential to keep the definition consistent and easy to modify. This framework seeks to make it simple and fast to define the core data structures needed for an application, and share that definition with other parts of the system.
- Actions are defined on an object, or a group of objects. These are exposed as webhooks / API endpoints to trigger actions.
- The logic and
-
Focus on Ergonomics - Tailwag relies heavily on traits and derive macros. The goal is to make sensible defaults for the general case, getting a standard, functional app ready with zero effort while also making it easy to override with custom behavior through trait implementations. In general, I have tried to avoid procedural (non-derive) macros that perform too much magic.
FAQ
Q: Why not use existing crates like axum
, actix-web
, diesel
, yew
or <other_library>
?
A: I am a strong believer in practice as a productivity tool. I started these libraries as a self-study learning exercise, and over time they've grown into something more useful.
Q: What's up with the macro crates?
A: The first macros I wrote were a mess of spaghetti code, and I've slowly been simplifying how I write them to make them more readable / maintainable. tl;dr: They're just a mess right now.
Currently I have two macro sub-crates for the crates that have custom macros,
one for the exports and one for the logic. I initially thought this would make
it easier to reuse and test the macros, given the compiler-enforced requirement
that proc_macro
exports be an entirely separate crate - but the reality is
that I just created an annoying hurdle to jump around when changing the macros.
I will be refactoring and simplify those macros in the future.
Q: Why were certain decisions made?
A: Sometimes the reason a decision made is just because it's what made sense at the time. There are some parts of the framework that I've rebuild a couple of times, some I've been happy with the way I did it at first, and others that I wish I had done differently but they are too baked in at this point to change easily. Such is the struggle of all software projects.
Q: I have another question.
A: Please file an issue or send me an email!
Can I use Tailwag in Production?
A: You are free to use Tailwag however you see fit, although it has some known limitations at this time. It is provided "as is", without warranty of any kind. If you use it in a Production application, make sure you test extensively.
LICENSE
Copyright (c) 2024 Nik Gilmore (@nikwithak)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Dependencies
~63–87MB
~1.5M SLoC