Skip to content

Serving Files and Caching

Aqueduct can serve files by returning the contents of a file as an HTTP response body.


Instances of FileController serve a directory from the filesystem through an HTTP interface. Any route that channels requests to an FileController must contain a * match-all token.

Controller get entryPoint {
  final router = Router();

  router.route("/files/*").link(() => FileController("public/"));

  return router;

The argument to FileController is the directory on the filesystem in which request paths will be resolved against. In the above example, an HTTP request with the path /files/image.jpg would return the contents of the file public/image.jpg.

Note that public/ does not have a leading slash - therefore, the directory public must be relative to the directory that the Aqueduct application was served from. In practice, this means you might have a directory structure like:


Adding a leading slash to the directory served by FileController will resolve it relative to the filesystem root.

If the requested path was a directory, the filename index.html will be appended to the path when searching for a file to return.

If a file does not exist, an FileController returns a 404 Not Found response.

Content-Type of Files

An FileController will set the content-type of the HTTP response based on the served files path extension. By default, it recognizes many common extensions like .html, .css, .jpg, .js. You may add content-types for extensions to an instance:

var controller = FileController("public/")
  ..setContentTypeForExtension("xml", ContentType("application", "xml"));

If there is no entry for an extension of a file being served, the content-type defaults to application/octet-stream. An FileController will never invoke any encoders from CodecRegistry, but it will GZIP data if the repository allows compression for the content-type of the file (see CodecRegistry.add and CodecRegistry.setAllowsCompression).


An FileController always sets the the Last-Modified header of the response to the last modified date according to the filesystem. If a request sends an If-Modified-Since header and the file has not been modified since that date, a 304 Not Modified response is sent with the appropriate headers.

You may provide Cache-Control headers depending on the path of the file being served. Here's an example that adds Cache-Control: public, max-age=31536000

var policy = CachePolicy(expirationFromNow: Duration(days: 365));
var controller = FileController("public/")
  ..addCachePolicy(policy, (path) => path.endsWith(".css"));

File Serving and Caching Outside of FileController

A file can be served by any controller by setting the body object of a Response with its contents:

var file = File("index.html");

// By loading contents into memory first...
var response = Response.ok(file.readAsStringSync())
  ..contentType = ContentType("application", "html");

// Or by streaming the contents from disk
var response = Response.ok(file.openRead())
  ..encodeBody = false
  ..contentType = ContentType("application", "html");

It is important to understand the how Aqueduct uses content-types to manipulate response bodies to serve file contents.

You may set the CachePolicy of any Response. Note that CachePolicy only modifies the Cache-Control header of a response. Headers like Last-Modified and ETag are not added.

var response = Response.ok("contents")
  ..cachePolicy = CachePolicy();