Hometus.io logo

tus Node.js server 2.0.0: integrate in all meta-frameworks and JS runtimes

Published on by Merlijn Vos

The Node.js tus server can now run in all meta-frameworks and Node.js compatible runtimes. Deprecated options have been removed, and the server is ESM-only. The packages now require Node.js >=20.19.0, the release that backported require(esm), allowing us to output ESM and import ESM packages without affecting consumers on CommonJS. But that’s not all, so let’s dive in.

What is tus?

tus is an HTTP-based protocol for resumable file uploads. Resumable means that an upload can be picked up again at any moment, after it was interrupted for whatever reason, without the need to re-upload the previous data. An interruption may happen on purpose, if the user wants to pause, or by accident in case of a network issue or server outage (or your cat deciding to take a nap on the keyboard).

The developers behind tus are currently working together with the HTTP working group inside the Internet Engineering Task Force (IETF) – the internet standards-setting organization that defines the network protocols powering the entire internet – to make resumable uploads an official standard across the web.

tus-node-server is an official implementation of the tus resumable upload protocol, although it does not support the IETF draft yet. It is capable of accepting uploads of all sorts and sizes, and storing them locally on disk, or remotely on Google Cloud Storage or AWS S3 (or any other S3-compatible storage system). Due to its modularization and extensibility, support for nearly any other cloud provider could easily be added.

Looking back

tus Node.js 1.0.0 brought modular packages, TypeScript, and stability. Since that major release, we’ve been shipping lots of improvements in minor versions, such as:

We did all this, while maintaining backwards compatibility. tus Node.js has been battle tested at scale in multiple large companies, including in a multi-tenant setup at Supabase.

These days, however, people run JavaScript servers in lots of places and environments. It has become quite common to not run your own Node.js server, but leverage new (serverless) runtimes, such as AWS Lambda, Cloudflare, Deno Deploy, or Bun. Thanks to the WinterTC, the Technical Committee on Web-interoperable Server Runtimes, we can – more or less everywhere – write servers with the web APIs: Request and Response.

Alternatively, you might be using a full-stack meta-framework, such as Next.js, Nuxt, React Router, SvelteKit, etc. In that case, you write your API or server logic there, with similar Request/Response request handlers.

When a server is written specifically for Node.js with http.IncomingMessage and http.ServerResponse handlers, there is no way to integrate in those environments and frameworks.

tus Node.js 2.0.0

Running anywhere where JavaScript runs

tus Node.js 2.0.0 can now run in all meta-frameworks, such as Next.js, Nuxt, React Router, SvelteKit, etc. The same goes for all Node.js-compatible runtime environments, such as AWS Lambda, Cloudflare, Bun, and Deno Deploy. Cloudflare Workers do not have node:fs, which means the @tus/file-store and the R2-compatible @tus/s3-store can’t run there.

This major version is a rewrite of all handlers to be based on Request and Response, as it’s possible to convert Node’s request/response objects to those, but not the other way around.

Use the new handleWeb() method for Request-based handlers, such as in Bun:

import { Server } from '@tus/server'
import { FileStore } from '@tus/file-store'

const tus = new Server({
  path: '/files',
  datastore: new FileStore({ directory: './files' }),
})

Bun.serve({
  routes: {
    '/files/*': (req) => tus.handleWeb(req),
  },
})

Use the handle() method for integrating into traditional Node.js frameworks, such as Fastify:

import fastify from 'fastify'
import { Server } from '@tus/server'
import { FileStore } from '@tus/file-store'

const app = fastify()
const tus = new Server({
  path: '/files',
  datastore: new FileStore({ directory: './files' }),
})
app.addContentTypeParser(
  'application/offset+octet-stream',
  // Leave body untouched
  (request, payload, done) => done(null),
)
app.all('/files', (req, res) => tus.handle(req.raw, res.raw))
app.all('/files/*', (req, res) => tus.handle(req.raw, res.raw))
app.listen(3000)

You can also run the server standalone:

import { Server } from '@tus/server'
import { FileStore } from '@tus/file-store'

const host = '127.0.0.1'
const port = 1080

const tus = new Server({
  path: '/files',
  datastore: new FileStore({ directory: './files' }),
})
tus.listen({ host, port })

ESM-only

All LTS releases of Node.js can now require(esm) from CommonJS, something that has been considered impossible for as long as most can remember. This is an incredible achievement that finally relieves maintainers from choosing whether to publish CommonJS, ESM, or both.

With this release, we bump the minimum required Node.js version from >=16 to >=20.19.0, the specific release that backported require(esm).

While we don’t depend on any ESM-only packages yet, it does give us the flexibility to do so should we need it, ensuring we can pick the best tools to offer the best uploading experience.

What is next?

With this release, the server is in really good shape. Nonetheless, some improvements might be on the horizon soon:

Take the tus server for a spin and let us know what you think!