Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Static files

Static files are sent to the client as they are, without processing: HTML pages, styles, scripts, images. Fastify does not serve them on its own; the official @fastify/static plugin does. For example, next to the API you can place a simple blog page that loads posts with fetch.

Installing

npm install @fastify/static

The public folder

Create a public folder in the project root and the file public/index.html:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Blog</title>
    <link rel="stylesheet" href="/style.css">
  </head>
  <body>
    <h1>Blog</h1>
    <ul id="posts"></ul>
  </body>
</html>

Put public/style.css with any styles next to it. The project structure:

blog-api/
  public/
    index.html
    style.css
  src/
    app.js

Registering

Register the plugin in src/app.js:

// src/app.js
import path from 'node:path';
import fastifyStatic from '@fastify/static';
 
app.register(fastifyStatic, {
  root: path.join(import.meta.dirname, '..', 'public'),
  prefix: '/',
});

root is an absolute path to the folder. import.meta.dirname is the folder of the current file (src), so '..' goes up to the project root. Open http://localhost:3000/ — the "Blog" page appears, and http://localhost:3000/style.css returns the stylesheet.

The prefix

The prefix option sets the start of file addresses. With prefix: '/assets/' the file public/style.css is served at /assets/style.css. A prefix keeps files from being confused with API routes.

Sending a file from a route

The plugin adds the reply.sendFile() method — use it to send a file from the root folder in an ordinary route:

// src/routes/pages.js
export default async function pageRoutes(app) {
  app.get('/about', async (request, reply) => {
    return reply.sendFile('about.html');
  });
}

Create public/about.html, register pageRoutes in src/app.js and open http://localhost:3000/about — the contents of that file appear.

Common mistake

Do not put a relative path such as 'public' in root: the plugin is designed for an absolute path, and a relative one depends on the folder the process was started in, so starting from another folder breaks file lookup. Always build the path with import.meta.dirname and path.join.

Thanks for reading!

Donate to the site — we work hard for you.