File access in Foxx

Files within the service folder should always be considered read-only. You should not expect to be able to write to your service folder or modify any existing files.

ArangoDB is primarily a database. In most cases the best place to store data is therefore inside the database, not on the file system.

Serving files

The most flexible way to serve files in your Foxx service is to simply pass them through in your router using the context object’s fileName method and the response object’s sendFile method:

router.get("/some/filename.png", function(req, res) {
  const filePath = module.context.fileName("some-local-filename.png");
  res.sendFile(filePath);
});

While allowing for greater control of how the file should be sent to the client and who should be able to access it, doing this for all your static assets can get tedious.

Alternatively you can specify file assets that should be served by your Foxx service directly in the service manifest using the files attribute:

"files": {
  "/some/filename.png": {
    "path": "some-local-filename.png",
    "type": "image/png",
    "gzip": false
  },
  "/favicon.ico": "bookmark.ico",
  "/static": "my-assets-folder"
}

Writing files

It is almost always an extremely bad idea to attempt to modify the filesystem from within a service:

  • The service folder itself is considered an implementation artefact and may be discarded and replaced without warning. ArangoDB maintains a canonical copy of each service internally to detect missing or damaged services and restore them automatically.

  • ArangoDB uses multiple V8 contexts to allow handling multiple Foxx requests in parallel. Writing to the same file in a request handler may therefore cause race conditions and result in corrupted data.

  • Writing to files outside the service folder introduces external state. In a cluster this will result in Coordinators no longer being interchangeable.

  • Writing to files during setup is unreliable because the setup script may be executed several times or not at all. In a cluster the setup script will only be executed on a single Coordinator.

Therefore it is almost always a better option to store files using a specialized, external file storage service and handle file uploads outside Foxx itself.

However in some cases it may be feasible to store smaller files directly in ArangoDB documents by using a separate collection.

Due to the way ArangoDB stores documents internally, you should not store file contents alongside other attributes that might be updated independently. Additionally, large file sizes will impact performance for operations involving the document and may affect overall database performance.

In production, you should avoid storing any files in ArangoDB or handling file uploads in Foxx. The following example will work for moderate amounts of small files but is not recommended for large files or frequent uploads or modifications.

To store files in a document you can simply convert the file contents as a Buffer to a base64-encoded string:

router.post('/avatars/:filename', (req, res) => {
  collection.save({
    filename: req.pathParams.filename,
    data: req.body.toString('base64')
  });
  res.status('no content');
});
router.get('/avatars/:filename', (req, res) => {
  const doc = collection.firstExample({
    filename: req.pathParams.filename
  });
  if (!doc) res.throw('not found');
  const data = new Buffer(doc.data, 'base64');
  res.set('content-type', 'image/png');
  res.set('content-length', data.length);
  res.write(data);
});