Use Stackables to build Platformatic applications
Platformatic Service and Platformatic DB offer a good starting point to create new applications. However, most developers or organizations might want to create reusable services or applications built on top of Platformatic. We call these reusable services "Stackables" because you can create an application by stacking services on top of them.
This is useful to publish the application on the public npm registry (or a private one!), including building your own CLI, or to create a specialized template for your organization to allow for centralized bugfixes and updates.
This process is the same one we use to maintain Platformatic DB and Platformatic Composer on top of Platformatic Service.
Creating a custom Service
We are creating the stackable foo.js
as follows:
const { schema, platformaticService } = require('@platformatic/service')
/** @type {import('fastify').FastifyPluginAsync<{}>} */
async function foo (app, opts) {
const text = app.platformatic.config.foo.text
app.get('/foo', async (request, reply) => {
return text
})
await app.register(platformaticService, opts)
app.get('/', async (request, reply) => {
return 'Your new root endpoint'
})
}
foo.configType = 'foo'
// break Fastify encapsulation
foo[Symbol.for('skip-override')] = true
// The schema for our configuration file
foo.schema = {
$id: 'https://example.com/schemas/foo.json',
title: 'Foo Service',
type: 'object',
properties: {
server: schema.server,
plugins: schema.plugins,
metrics: schema.metrics,
watch: {
anyOf: [schema.watch, {
type: 'boolean'
}, {
type: 'string'
}]
},
$schema: {
type: 'string'
},
extends: {
type: 'string'
},
foo: {
type: 'object',
properties: {
text: {
type: 'string'
}
},
required: ['text']
}
},
additionalProperties: false,
required: ['server']
}
// The configuration for the ConfigManager
foo.configManagerConfig = {
schema: foo.schema,
envWhitelist: ['PORT', 'HOSTNAME'],
allowToWatch: ['.env'],
schemaOptions: {
useDefaults: true,
coerceTypes: true,
allErrors: true,
strict: false
}
}
module.exports = foo
Note that the $id
property of the schema identifies the module in our system,
allowing us to retrieve the schema correctly.
It is recommended, but not required, that the JSON schema is actually
published in this location. Doing so allows tooling such as the VSCode
language server to provide autocompletion.
In this example, the schema
adds a custom top-level foo
property
that users can use to configure this specific module.
ESM is also supported.
Consuming a custom application
Consuming foo.js
is simple. We can create a platformatic.json
file as follows:
{
"$schema": "https://example.com/schemas/foo.json",
"extends": "./foo",
"server": {
"port": 0,
"hostname": "127.0.0.1"
},
"foo": {
"text": "Hello World"
}
}
Note that we must specify both the $schema
property and extends
.
The module specified with extends
can also be any modules published on npm and installed via your package manager.
extends
is the name of the module we are actually "stacking" (extending) on top of.
The property module
can also be used, but it is deprecated. In both cases, be sure that the property is allowed in the stackable schema (in this example in foo.schema
)
Building your own CLI
If you want to create your own CLI for your service on top of a Stackable you can just importing the base module and then start it, e.g.:
import base from 'mybasemodule' // Import here your base module
import { start } from '@platformatic/service'
import { printAndExitLoadConfigError } from '@platformatic/config'
await start(base, process.argv.splice(2)).catch(printAndExitLoadConfigError)
This is the same as running with platformatic CLI, the platformatic.json
file will be loaded from the current directory.