Tide

repository·main·Indexed 26 days ago

https://github.com/http-rs/tide

A minimal and pragmatic Rust web application framework designed for rapid development of asynchronous web applications and APIs. It features a modular ecosystem for TLS, template engines, authentication, and middleware, and integrates with serde for JSON handling. Version 0.17.0-beta.1.

Tokens
5K
Snippets
17
Records
24
Agent score
90%

What's inside tide

  1. Install Tide and dependencies

    main

    To build a web application with Tide, you need to add tide, an async runtime (like async-std), and serde for serialization/deserialization to your Cargo.toml file.

    # Example, use the version numbers you need
    tide = "0.17.0"
    async-std = { version = "1.8.0", features = ["attributes"] }
    serde = { version = "1.0", features = ["derive"] }
  2. Initialize a Tide Server

    main

    You can create a new Tide server in two ways:

    1. Without shared state: Use tide::new() to create a server with the unit type () as state.
    2. With shared state: Use tide::with_state(state) to provide a custom, user-defined state. This state is available as a shared reference to all application endpoints via req.state().

    Note: The state must implement Clone + Send + Sync + 'static.

    // Without state
    let mut app = tide::new();
    
    // With shared state
    #[derive(Clone)]
    struct State {
        name: String,
    }
    
    let state = State { name: "Nori".to_string() };
    let mut app = tide::with_state(state);
  3. Create a basic JSON API with Tide

    main

    This example demonstrates how to initialize a Tide application, define a route that accepts POST requests, and extract a JSON body into a Rust struct using req.body_json().

    use tide::Request;
    use tide::prelude::*;
    
    #[derive(Debug, Deserialize)]
    struct Animal {
        name: String,
        legs: u16,
    }
    
    #[async_std::main]
    async fn main() -> tide::Result<()> {
        let mut app = tide::new();
        app.at("/orders/shoes").post(order_shoes);
        app.listen("127.0.0.1:8080").await?;
        Ok(())
    }
    
    async fn order_shoes(mut req: Request<()>) -> tide::Result {
        let Animal { name, legs } = req.body_json().await?;
        Ok(format!("Hello, {}! I've put in an order for {} shoes", name, legs).into())
    }
  4. Tide ecosystem and community resources

    main

    Tide is highly modular and supports various third-party crates for extending functionality:

    Listeners (TLS/HTTPS)

    • tide-rustls: TLS support based on async-rustls.
    • tide-acme: HTTPS with automatic certificates via Let's Encrypt.

    Template Engines

    • tide-tera, tide-handlebars, and askama.

    Routers

    • tide-fluent-routes.

    Authentication

    • tide-http-auth, tide-openidconnect, and tide-jwt.

    Middleware

    • tide-compress: Compression.
    • tide-sqlx: SQLx pooled connections & transactions.
    • tide-websockets: WebSocket support.
    • tide-csrf: CSRF protection.
    • tide-flash: Flash messages.
    • driftwood: HTTP logging.

    Session Stores

    • async-redis-session, async-sqlx-session, and async-mongodb-session.
  5. Manage cookies in Tide requests and responses

    main

    Tide provides a middleware pattern for handling cookies. When using cookie management, you can retrieve cookies from an incoming Request and set new cookies on an outgoing Response using insert_cookie.

    To retrieve a cookie, use req.cookie("name"). To set a cookie, create a Cookie object and use res.insert_cookie(cookie).

    # use tide::{Request, Response, StatusCode};
    # use tide::http::cookies::Cookie;
    # use tide::prelude::*;
    
    let mut app = tide::Server::new();
    
    // Retrieve a cookie from a request
    app.at("/get").get(|req: Request<()>| async move {
        Ok(req.cookie("testCookie").unwrap().value().to_string())
    });
    
    // Set a cookie in a response
    app.at("/set").get(|_| async {
        let mut res = Response::new(StatusCode::Ok);
        res.insert_cookie(Cookie::new("testCookie", "NewCookieValue"));
        Ok(res)
    });
  6. Handle JSON requests and responses

    main

    Tide integrates with serde to allow easy parsing of JSON bodies from requests using req.body_json().await?. You can respond with strings, which are automatically converted into Response objects.

    use tide::Request;
    use tide::prelude::*;
    
    #[derive(Debug, Deserialize)]
    struct Animal {
        name: String,
        legs: u16,
    }
    
    async fn order_shoes(mut req: Request<()>) -> tide::Result {
        let Animal { name, legs } = req.body_json().await?;
        Ok(format!("Hello, {}! I've put in an order for {} shoes", name, legs).into())
    }
  7. Manage Response errors

    main

    A Response can carry an error, which is useful for middleware to signal error states.

    • set_error(&mut self, error: impl Into<Error>): Sets or overwrites the response's error.
    • error(&self) -> Option<&Error>: Returns a reference to the error if one exists.
    • take_error(&mut self) -> Option<Error>: Returns the error and removes it from the response.
    • downcast_error<E>(&self) -> Option<&E>: Attempts to downcast the error to a specific type E.
    # use tide::Response;
    # use std::io::ErrorKind;
    # use async_std::task;
    # fn main() {
    # task::block_on(async {
    let error = std::io::Error::new(ErrorKind::Other, "oh no!");
    let error = tide::http::Error::from(error);
    let mut res = Response::new(400);
    res.set_error(error);
    
    if let Some(err) = res.downcast_error::<std::io::Error>() {
      // Access the downcast error
    }
    # });
    # });
  8. Define an HTTP request handler as an Endpoint

    main

    In Tide, an endpoint is a function that takes a tide::Request<State> as an argument and returns a type that implements Into<Response>. While the Endpoint trait is automatically implemented for Fn types, you typically define endpoints as async functions for better ergonomics.

    Common return types for endpoints include tide::Result<String>, tide::Result<T> (where T implements Into<Response>), or tide::Result<Response>.

    async fn hello(_req: tide::Request<()>) -> tide::Result<String> {
        Ok(String::from("hello"))
    }
    
    let mut app = tide::Server::new();
    app.at("/hello").get(hello);
  9. Implement the Listener trait for custom transports

    main

    To provide a custom HTTP transport to a Tide application, implement the Listener<State> trait. A Listener is responsible for binding to a network address and accepting incoming connections.

    Note: To provide a Listener to Tide, you must also implement the ToListener trait that outputs your specific Listener type.

    Required methods:

    • async fn bind(&mut self, app: Server<State>) -> io::Result<()>: Opens the necessary network ports/sockets. Must be called before accept.
    • async fn accept(&mut self) -> io::Result<()>: Starts accepting incoming connections. Must be called after bind succeeds.
    • fn info(&self) -> Vec<ListenInfo>: Returns information about the connection(s).
    #[async_trait]
    pub trait Listener<State>: Debug + Display + Send + 'static
    where
        State: Send + Sync + 'static,
    {
        async fn bind(&mut self, app: Server<State>) -> io::Result<()>;
        async fn accept(&mut self) -> io::Result<()>;
        fn info(&self) -> Vec<ListenInfo>;
    }
  10. Set the Response status code

    main

    Use set_status to update the HTTP status code of an existing response. You can pass either a tide::StatusCode or a u16 integer. Note that set_status will panic if the provided integer is not a valid HTTP status code.

    # use tide::{StatusCode, Response};
    let mut response = Response::new(StatusCode::Ok);
    
    response.set_status(418); // valid u16
    assert_eq!(response.status(), StatusCode::ImATeapot);
    
    response.set_status(StatusCode::NonAuthoritativeInformation);
    assert_eq!(response.status(), StatusCode::NonAuthoritativeInformation);
  11. Use ListenInfo to inspect connection details

    main

    When implementing a Listener, use ListenInfo to expose details about the connection. This is used by Tide to understand the transport layer and connection strings.

    Fields available via methods:

    • connection(): Returns the connection string (e.g., 127.0.0.1:8080).
    • transport(): Returns the underlying transport type (e.g., "tcp", "uds").
    • is_encrypted(): Returns true if the connection uses TLS.
    #[derive(Debug, Clone)]
    pub struct ListenInfo {
        conn_string: String,
        transport: String,
        tls: bool,
    }
    
    impl ListenInfo {
        pub fn new(conn_string: String, transport: String, tls: bool) -> Self { ... }
        pub fn connection(&self) -> &str { ... }
        pub fn transport(&self) -> &str { ... }
        pub fn is_encrypted(&self) -> bool { ... }
    }
  12. Test endpoints directly with `respond()`

    main

    The respond(req) method allows you to pass a Request directly to the server and receive a Response. This is highly useful for unit testing endpoints without needing to open actual network ports.

    # use tide::http::{Url, Method, Request, Response};
    let mut app = tide::new();
    app.at("/").get(|_| async { Ok("hello world") });
    
    let req = Request::new(Method::Get, Url::parse("https://example.com")?);
    let res: Response = app.respond(req).await?;
    
    assert_eq!(res.status(), 200);