hapi — How to Serve Static Files (Images, JS, CSS, etc.)

Within the previous tutorial, you learned how to add routes to your application and handle requests with their related responses. When building web applications, the need to serve files arises quickly. And with that, you also need routes to respond requests with file resources. If you’re serving HTML views to your client, you might want to display images or provide JavaScript files to enhance the client side functionality.

This guide walks you through the setup of hapi to serve static files like images, JavaScript files, compressed archives (.zip, .tar, etc.).

Before diving into the details, have a look at the series outline and find posts that match your interests and needs.

hapi Series Overview

Prepare Your Server

Hapi doesn’t ship with functionality to serve static files by default. That keeps the actual framework footprint small and you can add this feature easily by using the inert plugin. We also have tutorials that guide you through the extension hapi’s functionality using plugins and how to create your own custom hapi plugin.

Once you registered the inert plugin to your hapi server instance, it adds new handler methods to serve files and directories and also decorates the reply object with a file method to serve file resources.

hapi v17

const Hapi = require('hapi')

// create new server instance
const server = new Hapi.Server({  
  host: 'localhost',
  port: 3000
})

async function liftOff () {  
  await server.register({
    plugin: require('inert')
  })

  await server.start()
  console.log('Server started at: ' + server.info.uri)
}

liftOff()  

hapi v16

var Hapi = require('hapi')

// create new server instance
var server = new Hapi.Server()

// add server’s connection information
server.connection({  
  host: 'localhost',
  port: 3000
})

server.register({  
  register: require('inert')
}, function(err) {
  if (err) throw err

  server.start(function(err) {
    console.log('Server started at: ' + server.info.uri)
  })
})

The snippet above just illustrates how to prepare your hapi server to serve static files from disk. Within the following sections, you’ll learn how to send files as response and serve files from a directory.

Send Any File (Images, JS, etc.)

When using HTML views, you probably want to define specific CSS or JS files that can be requested from your server. You may consume some of your 3rd party dependencies from CDN’s and others are shipped with your project locally. If you want to serve a specific file via a given route, define the filename including file extension as a route path. Afterwards, you can utilize the reply.file() method to serve the requested file from local disk.

In the following example, we’ve reduced the exemplary code snippet to just represent the route definition. The most interesting part is the handler function that responds the request using the reply.file() function and serves the mylocaljavascript.js file from the given file location on your server’s disc.

hapi v17

server.route({  
  method: 'GET',
  path: '/mylocaljavascript.js',
  handler: (request, h) => {
    // h.file() expects the file path as parameter
    return h.file('../path/to/my/localjavascript.js')
  }
})

hapi v16

server.route({  
  method: 'GET',
  path: '/mylocaljavascript.js',
  handler: function (request, reply) {
    // reply.file() expects the file path as parameter
    reply.file('../path/to/my/localjavascript.js')
  }
})

As already mentioned earlier, the reply.file() method expects the local file’s path as a parameter. Afterwards, hapi creates the response and returns the requested file (if found).

Serve Files From a Directory

With the help of inert, you can serve all files located in a selected directory. Using path parameters for a route definition, you’re able to just define a single route that serves a given type of requests. The following example defines a route that serves JS files for requests that come in at /js/<file>.

Actually, the path parameter name doesn’t matter. Using the asterisk extension does not restrict the file depth and allows you to make use of files located in subfolders. In case you only want to support root level files,remove the asterisk from the path.

server.route({  
  method: 'GET',
  path: '/js/{file*}',
  handler: {
    directory: { 
      path: 'public/js'
    }
  }
})

The directory handler simplifies the route configuration and you just need to provide the correct path to the local JS files on your server’s disk. Once your server receives a request to serve a specific JS file, inert looks up the defined path to find the requested file. If available, the file gets served as a response for the current request. If the file isn’t available at the specified location, your server responds with a 404 Not Found.

By default, inert doesn’t list the directory contents. If a file wasn’t found, you’ll receive a 404. Using the listing: true option besides the path property for a directory handler, you can define that any request to /js is able to list the contents of /public/js directory.

server.route({  
  method: 'GET',
  path: '/js/{file*}',
  handler: {
    directory: { 
      path: 'public/js',
      listing: true
    }
  }
})

The route snippet above allows users list the contents of your public/js folder. The listing property is false by default.

The directory handler has further options, like:

  • showHidden: that determines if hidden files can be served
  • redirectToSlash: determines that requests without trailing slash get redirected to the “with slash pendant”, which is useful for the resolution of relative paths
  • lookupCompressed: tells the file processor to search for the requested file in compressed .gz version
  • etagMethod: defines the method to calculate the ETag header. Please find more details about the directory options within the inert GitHub repository.

Outlook

That’s the foundation you need to serve various types of files for incoming requests. You’ve learned how to prepare and add the capabilities to respond with file resources using the inert plugin. Furthermore, you know that there are multiple options to respond file requests using either the reply.file() method or just define the path to a directory providing the actual files.

The next tutorial shifts direction to authentication and how to implement a cookie-based authentication and remember the users.

We appreciate feedback and love to help you if there’s a question on your mind. Let us know in the comments or on twitter @futurestud_io.

Make it rock & enjoy coding!


Additional Resources

  • inert — static file and directory handler plugin for hapi

Explore the Library

Find interesting tutorials and solutions for your problems.