How to migrate a Fastify app to Platformatic Service

Search for a command to run...

No comments yet. Be the first to comment.
Nitro is a server toolkit used by many JavaScript applications. You can use it on its own for APIs or full-stack servers, or combine it with Vite to get a familiar frontend workflow with a Nitro serve

All durable execution engines give the same versioning advice: pin your runs. Temporal pins runs to worker builds. Vercel's Workflow SDK pins each run to the deployment that started it. Azure Durable

Eve organizes AI agents in a simple way: use Markdown for instructions and skills, TypeScript for tools, and separate files for channels, schedules, and subagents. Behind this setup, Eve relies on the

Picture an online store launching a new product and sending out a mailing list campaign. Thousands of users click the same link at once. The product page, built with a Node.js app like Next.js, needs

How Platformatic ICC Outperforms AWS ECS Target Tracking and Step Scaling

Building production ready Node.js application with Fastify can require a certain amount of boilerplate code. This is a side effect of some of Fastify's technical principles:
Platformatic Service is the natural evolution of the build-it-from-scratch Fastify development experience. It provides a solid foundation for building Node.js applications on top of Fastify, with best practices baked in.
See the Building apps with Platformatic Service section of this guide to learn more about the built-in features.
The good news is that the path to migrate a Fastify application to use Platformatic Service is fairly straightforward. This guide covers some of the things you'll need to know when migrating an application, as well as tips on different migration approaches.
This guide assumes that you have some experience building applications with the Fastify framework. If you'd like to learn more about about building web applications with Fastify, we recommend taking a look at:
For the purpose of this guide, we have a basic example Fastify application. Although this app has a specific structure, the migration steps covered in this guide can generally be applied to any Fastify application.
The code for the example Fastify and migrated Platformatic Service applications is available on GitHub.
Here's the structure of the example Fastify application:
├── app.js
├── package.json
├── plugins
│ └── data-source.js
├── routes
│ ├── movies.js
│ └── quotes.js
├── server.js
└── test
└── routes.test.js
It has the following dependencies:
// package.json
"dependencies": {
"fastify": "^4.17.0",
"fastify-plugin": "^4.5.0"
}
The application has a plugin that decorates the Fastify server instance, as well as two Fastify plugins which define API routes. Here's the code for them:
// plugins/data-source.js
import fastifyPlugin from 'fastify-plugin'
/** @param {import('fastify').FastifyInstance} app */
async function dataSource (app) {
app.decorate('movies', [
'Jaws',
'Star Wars',
'The Wizard of Oz'
])
app.decorate('quotes', [
'You\'re gonna need a bigger boat.',
'May the Force be with you.',
'Toto, I\'ve got a feeling we\'re not in Kansas anymore.'
])
}
export default fastifyPlugin(dataSource)
fastify-pluginis used to to prevent Fastify from creating a new encapsulation context for the plugin. This makes the decorators that are registered in thedataSourceplugin available in the route plugins. You can learn about this fundamental Fastify concept in the Fastify Encapsulation documentation.
// routes/movies.js
/** @param {import('fastify').FastifyInstance} app */
export default async function movieRoutes (app) {
app.get('/', async (request, reply) => {
return app.movies
})
}
// routes/quotes.js
/** @param {import('fastify').FastifyInstance} app */
export default async function quotesRoutes (app) {
app.get('/', async (request, reply) => {
return app.quotes
})
}
The route plugins aren't registering anything that needs to be available in other plugins. They have their own encapsulation context and don't need to be wrapped with
fastify-plugin.
There's also a buildApp() factory function in app.js, which takes care of creating a new Fastify server instance and registering the plugins and routes:
// app.js
import fastify from 'fastify'
export async function buildApp (options = {}) {
const app = fastify(options)
app.register(import('./plugins/data-source.js'))
app.register(import('./routes/movies.js'), { prefix: '/movies' })
app.register(import('./routes/quotes.js'), { prefix: '/quotes' })
return app
}
And server.js, which calls the buildApp function to create a new Fastify server, and then starts it listening:
// server.js
import { buildApp } from './app.js'
const port = process.env.PORT || 3042
const host = process.env.HOST || '127.0.0.1'
const options = {
logger: {
level: 'info'
}
}
const app = await buildApp(options)
await app.listen({ port, host })
As well as a couple of tests for the API routes:
// tests/routes.test.js
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { buildApp } from '../app.js'
test('Basic API', async (t) => {
const app = await buildApp()
t.after(async () => {
await app.close()
})
await t.test('GET request to /movies route', async () => {
const response = await app.inject({
method: 'GET',
url: '/movies'
})
assert.equal(response.statusCode, 200)
assert.deepEqual(response.json(), [
'Jaws',
'Star Wars',
'The Wizard of Oz'
])
})
await t.test('GET request to /quotes route', async () => {
const response = await app.inject({
method: 'GET',
url: '/quotes'
})
assert.equal(response.statusCode, 200)
assert.deepEqual(response.json(), [
'You\'re gonna need a bigger boat.',
'May the Force be with you.',
'Toto, I\'ve got a feeling we\'re not in Kansas anymore.'
])
})
})
These tests are using the built in Node.js test runner, node:test. They can be run with the command: node --test --test-reporter=spec test/*.test.js.
The
@paramlines in this application code are JSDoc blocks that import theFastifyInstancetype. This allows many code editors to provide auto-suggest, type hinting and type checking for your code.
To migrate your Fastify app to Platformatic Service, create a new Platformatic Service app with:
npm create platformatic@latest
Be sure to select Service as the project type. Once the project has been created, you can delete the example plugins and routes directories.
The configuration for the Platformatic Service app is stored in platformatic.service.json.
The generated configuration is set up to load plugins from the plugins and routes directories:
// platformatic.service.json
"plugins": {
"paths": [
"./plugins",
"./routes"
]
}
The value for any configuration setting in platformatic.service.json can be replaced with an environment variable by adding a placeholder, for example {PLT_SERVER_LOGGER_LEVEL}. In development, environment variables are automatically loaded by your Platformatic Service app from a .env file in the root directory of your app. In production, you'll typically set these environment variables using a feature provided by your hosting provider.
See the Platformatic Service documentation for Environment variable placeholders to learn more about how this works.
If you're using ES modules in the Fastify application code that you'll be migrating, ensure that there's a type field in package.json set to module:
npm pkg set type=module
If your Fastify application has a script with a factory function to create and build up a Fastify server instance, you can refactor it into a Fastify plugin and use it in your Platformatic Service app.
Here are a few things to consider while refactoring it:
server block in platformatic.service.json. These options will be passed through directly by Platformatic Service when it creates a Fastify server instance.export default async function rootPlugin (app, options) { ... }"plugins": {
"paths": ["./root-plugin.js"]
}
"plugins": {
"paths": [
{
"path": "./root-plugin.js",
"options": {
"someOption": true
}
}
]
}
Copy over the plugins directory from your Fastify app. You shouldn't need to make any modifications for them to work with Platformatic Service.
Platformatic Service provides a configuration setting which enables you to disable encapsulation for a plugin, or all the plugins within a directory. This will make any decorators or hooks that you set in those plugins available to all other plugins. This removes the need for you to wrap your plugins with fastify-plugin.
To disable encapsulation for all plugins within the plugins directory, you would set your plugins configuration like this in platformatic.service.json:
// platformatic.service.json
"plugins": {
"paths": [
{
"path": "./plugins",
"encapsulate": false
},
"./routes"
]
}
You can learn more about plugin encapsulation in the Fastify Plugins Guide.
Copy over the routes directory from your Fastify app.
If you're registering routes in your Fastify application with full paths, for example /movies, you won't need to make any changes to your route plugins.
If you're using the prefix option when registering route plugins in your Fastify application, for example:
app.register(import('./routes/movies.js'), { prefix: '/movies' })
You can achieve the same result with Platformatic Service by using file-system based routing. With the following directory and file structure:
routes/
├── movies
│ └── index.js
└── quotes
└── index.js
Assuming that both of the route files register a / route, these are the route paths that will be registered in your Platformatic Service app:
/movies
/quotes
With the example Fastify application, this would mean copying the route files over to these places in the Platformatic Service app:
routes/movies.js -> routes/movies/index.js
routes/quotes.js -> routes/quotes/index.js
How does this work? Plugins are loaded with the
@fastify/autoloadFastify plugin. ThedirNameRoutePrefixplugin option for@fastify/autoloadis enabled by default. This means that "routes will be automatically prefixed with the subdirectory name in an autoloaded directory".
If you'd prefer not to use file-system based routing with Platformatic Service, you can add prefixes to the paths for the routes themselves (see Explicit route paths).
If you're using @fastify/autoload in your Fastify application, there are a couple of approaches you can take when migrating the app to Platformatic Service:
plugins in your Platformatic Service app's platformatic.service.json. It will then take care of loading your routes and plugins for you with @fastify/autoload (configuration documentation).@fastify/autoload directly with a little refactoring. See the tips in the Refactoring Fastify server factories section.You'll generally use the Platformatic CLI to start your Platformatic Service app (npx platformatic start). However for testing, you can use the programmatic API provided by Platformatic Service. This allows you to load your app in your test scripts and then run tests against it.
If you copy over the tests from your existing Fastify app, they will typically only require a small amount of refactoring to work with Platformatic Service.
The example Fastify app has a buildApp() factory function which creates a Fastify server instance. The import line for that function can be removed from tests/routes.test.js:
// tests/routes.test.js
import { buildApp } from '../app.js'
And replaced with an import of the buildServer() function from @platformatic/service:
// tests/routes.test.js
import { buildServer } from '@platformatic/service'
You can then load your Platformatic Service app like this:
const app = await buildServer('./platformatic.service.json')
If you have logged enabled for your Platformatic Service app, you'll probably want to disable the logging in your tests to remove noise from the output that you receive when you run your tests.
Instead of passing the path to your app's configuration to buildServer(), you can import the app configuration and disable logging:
// tests/routes.test.js
import serviceConfig from '../platformatic.service.json' assert { type: 'json' }
serviceConfig.server.logger = false
Then pass that serviceConfig configuration object to the buildServer() function:
// tests/routes.test.js
const app = await buildServer(serviceConfig)
Import assertions — the
assert { type: 'json' }syntax — are not a stable feature of the JavaScript language, so you'll receive warning messages from Node.js when running your tests. You can disable these warnings by passing the--no-warningsflag tonode.
Because Platformatic Service is built on top of the Fastify framework, you're able to use the full functionality of the Fastify framework in your Platformatic Service app. This includes:
Platformatic Service also provides many other features that are built on top of Fastify.
fastify-metrics@fastify/under-pressure@fastify/swagger and @fastify/swagger-uimercurius@fastify/corsAll Platformatic Service features are fully configurable via platformatic.service.json.
See the Platformatic Service Configuration documentation for all of the features which can be configured.
The documentation for Platformatic Service is a helpful reference when building a Platformatic Service app.
You want to be confident that you understand how your applications work. In this video you'll learn about the parts that make up a Platformatic application, what each part does, and how they fit together.
Our series of Platformatic How-to videos can help get you up and running building apps with Platformatic open-source tools.
Got questions or need help migrating your Fastify app to use Platformatic Service? Drop by our Discord server and we'll be happy to help you.