Serving static files

In this section of the backend part of the workshop we'll learn how to serve static files with Actix Web and Shuttle.

The main goal here is to serve the statics files present in a folder called static.

So the API will serve statics in the root path / and the API endpoints in the /api path.

For this to happen we will need to refactor a little bit our api-shuttle main code.

Shuttle dependencies

Read the Shuttle documentation for static files.

Some of the caveats that you will find explained there will apply to us as we are using a workspace, but let's start from the beginning.

Let's add the shuttle-static-folder and the actix-files dependencies to our api-shuttle crate.

[dependencies]
# static
actix-files = "0.6.6"

Serving the static files

Now, let's refactor our main.rs file to serve the static files.

Let's modify our ServiceConfig to serve static files in the / path and the API in the /api path:

- cfg.app_data(film_repository)
-   .configure(api_lib::health::service)
-   .configure(api_lib::films::service::<api_lib::film_repository::PostgresFilmRepository>);
+ cfg.service(
+     web::scope("/api")
+         .app_data(film_repository)
+         .configure(api_lib::health::service)
+         .configure(
+             api_lib::films::service::<api_lib::film_repository::PostgresFilmRepo+ sitory>,
+         ),
+ )
+ .service(Files::new("/", "static").index_file("index.html"));

Final Code

#![allow(unused)]
fn main() {
use actix_web::web::{self, ServiceConfig};
use shuttle_actix_web::ShuttleActixWeb;
use shuttle_runtime::CustomError;
use sqlx::Executor;
use std::path::PathBuf;

#[shuttle_runtime::main]
async fn actix_web(
    #[shuttle_shared_db::Postgres] pool: sqlx::PgPool,
    #[shuttle_static_folder::StaticFolder(folder = "static")] static_folder: PathBuf,
) -> ShuttleActixWeb<impl FnOnce(&mut ServiceConfig) + Send + Clone + 'static> {
    // initialize the database if not already initialized
    pool.execute(include_str!("../../db/schema.sql"))
        .await
        .map_err(CustomError::new)?;

    let film_repository = api_lib::film_repository::PostgresFilmRepository::new(pool);
    let film_repository = web::Data::new(film_repository);

    let config = move |cfg: &mut ServiceConfig| {
        cfg.service(
            web::scope("/api")
                .app_data(film_repository)
                .configure(api_lib::health::service)
                .configure(
                    api_lib::films::service::<api_lib::film_repository::PostgresFilmRepository>,
                ),
        )
        .service(Files::new("/", "static").index_file("index.html"));
    };

    Ok(config.into())
}
}

You will get a runtime error:

[Running 'cargo shuttle run']
    Building /home/roberto/GIT/github/robertohuertasm/devbcn-dry-run
   Compiling api-shuttle v0.1.0 (/home/roberto/GIT/github/robertohuertasm/devbcn-dry-run/api/shuttle)
    Finished dev [unoptimized + debuginfo] target(s) in 9.00s
2023-07-02T18:49:07.514534Z ERROR cargo_shuttle: failed to load your service error="Custom error: failed to provision shuttle_static_folder :: StaticFolder"
[Finished running. Exit status: 1]

That's mainly because the static folder doesn't exist yet.

Create a folder called static in the api-shuttle crate and add a file called index.html with this content:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Hello Shuttle</title>
  </head>
  <body>
    Hello Shuttle
  </body>
</html>

Now if you browse to http://localhost:8000 you should be able to see the index.html file.

Warning

Remember that we have changed the path for the API to /api so you will need to change that too in your api.http file or Postman configuration.

Ignoring the static folder

As the static folder will be generated by the frontend, we don't want to commit it to our repository.

Add this to the .gitignore file:

# Ignore the static folder
static/

Now, to solve a Shuttle issue affecting static folders in workspaces, we need to create a .ignore file in the root folder with the following content:

!static/

Commit your changes:

git add .
git commit -m "serve static files"

Now, in order to deploy to the cloud and avoid having issues with the static folder not being found (remember there's currently an issue in the Shuttle static folder implementation), copy the static folder to the root of your project and deploy:

cargo shuttle deploy