Platformatic: Node.js Backend Platform Guide
PROGRAMMING LANGUAGES March 11, 2026, 11:30 p.m.

Platformatic: Node.js Backend Platform Guide

Platformatic is a modern, opinionated framework that turns Node.js into a production‑ready backend platform with minimal boilerplate. It bundles powerful features—automatic OpenAPI generation, built‑in validation, and seamless database integration—while still giving you full control over the underlying Express server. Whether you’re building a quick prototype or a large‑scale microservice, Platformatic’s declarative configuration lets you focus on business logic instead of wiring up middleware.

Why Choose Platformatic?

First, Platformatic embraces convention over configuration, meaning most decisions are already made for you. It ships with sensible defaults for logging, error handling, and security, which reduces the time spent on repetitive setup tasks. Second, the platform is built on top of popular, battle‑tested libraries like fastify and pg, so you get performance without reinventing the wheel.

Third, the declarative platformatic.json file acts as a single source of truth for routes, services, and plugins. This makes onboarding new team members a breeze—just glance at the config and you know how the API is structured. Finally, Platformatic’s CLI offers one‑click scaffolding, live reload, and integrated testing utilities, streamlining the entire development lifecycle.

Getting Started: Project Scaffold

Install the CLI globally and generate a fresh project with a single command. The CLI will create a folder structure, install dependencies, and add a starter platformatic.json file.

npm install -g @platformatic/cli
platformatic init my-service
cd my-service
npm install

After the scaffold finishes, you’ll see a src/ directory with a services/ folder, a plugins/ folder, and the central configuration file. Run the development server to verify everything works.

npm run dev
# → Server listening on http://localhost:3042

The default endpoint /status returns a JSON payload confirming the service is alive. Open the URL in your browser or use curl to see the response.

Core Concepts

Configuration File

The heart of a Platformatic project is platformatic.json. It defines the server port, database connections, routes, and plugins. Below is a trimmed example that sets up a PostgreSQL connection and an HTTP server.

{
  "server": {
    "listen": {
      "host": "0.0.0.0",
      "port": 3042
    }
  },
  "db": {
    "connectionString": "postgres://user:pass@localhost:5432/mydb"
  },
  "services": [
    {
      "id": "users",
      "path": "/users",
      "module": "./src/services/users.js"
    }
  ]
}

Notice how each service points to a module that exports an async function. Platformatic will automatically mount the service at the specified path and generate OpenAPI documentation for it.

Plugins

Plugins are tiny, reusable pieces of middleware that can be shared across services. They live in the plugins/ directory and are referenced in the config file. For example, a simple request‑logging plugin might look like this:

// src/plugins/request-logger.js
module.exports = async function (app, opts) {
  app.addHook('onRequest', async (request, reply) => {
    console.log(`[${new Date().toISOString()}] ${request.method} ${request.url}`);
  });
};

Register the plugin globally in platformatic.json under the plugins array, and every incoming request will be logged without additional code in each service.

Services

A service is essentially a collection of route handlers that operate on a specific domain model. Platformatic expects each service to export a function that receives the Fastify instance and a context object. The context provides access to the database pool, configuration, and any shared utilities.

// src/services/users.js
module.exports = async function (app, { db }) {
  // GET /users
  app.get('/', async (request, reply) => {
    const { rows } = await db.query('SELECT id, name, email FROM users');
    return rows;
  });

  // POST /users
  app.post('/', async (request, reply) => {
    const { name, email } = request.body;
    const { rows } = await db.query(
      'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *',
      [name, email]
    );
    reply.code(201);
    return rows[0];
  });
};

The service automatically inherits JSON body parsing, error handling, and OpenAPI generation. You can now hit GET /users or POST /users without writing any extra boilerplate.

Building a Real‑World API: A Todo Service

Let’s walk through a practical example: a Todo API that supports CRUD operations, validation, and JWT authentication. This example showcases three core Platformatic features—validation schemas, authentication plugins, and relational queries.

Step 1: Define the Todo Service

Create src/services/todos.js. The service will expose endpoints for listing, creating, updating, and deleting todos. Each todo belongs to a user, so we’ll join the users table to enforce ownership.

// src/services/todos.js
module.exports = async function (app, { db }) {
  // List all todos for the authenticated user
  app.get('/', async (request, reply) => {
    const userId = request.user.id;
    const { rows } = await db.query(
      'SELECT id, title, completed FROM todos WHERE user_id = $1',
      [userId]
    );
    return rows;
  });

  // Create a new todo
  app.post('/', async (request, reply) => {
    const { title } = request.body;
    const userId = request.user.id;
    const { rows } = await db.query(
      'INSERT INTO todos (title, user_id) VALUES ($1, $2) RETURNING *',
      [title, userId]
    );
    reply.code(201);
    return rows[0];
  });

  // Update a todo's completion status
  app.patch('/:id', async (request, reply) => {
    const { id } = request.params;
    const { completed } = request.body;
    const userId = request.user.id;
    const { rows } = await db.query(
      `UPDATE todos SET completed = $1
       WHERE id = $2 AND user_id = $3
       RETURNING *`,
      [completed, id, userId]
    );
    if (rows.length === 0) {
      reply.code(404);
      return { error: 'Todo not found' };
    }
    return rows[0];
  });

  // Delete a todo
  app.delete('/:id', async (request, reply) => {
    const { id } = request.params;
    const userId = request.user.id;
    const { rowCount } = await db.query(
      'DELETE FROM todos WHERE id = $1 AND user_id = $2',
      [id, userId]
    );
    if (rowCount === 0) {
      reply.code(404);
      return { error: 'Todo not found' };
    }
    reply.code(204);
  });
};

Step 2: Add Validation Schemas

Platformatic can automatically validate request bodies against JSON Schema definitions. Create a schemas/ folder and add todo-create.json and todo-update.json.

// schemas/todo-create.json
{
  "type": "object",
  "required": ["title"],
  "properties": {
    "title": { "type": "string", "minLength": 1, "maxLength": 200 }
  },
  "additionalProperties": false
}
// schemas/todo-update.json
{
  "type": "object",
  "required": ["completed"],
  "properties": {
    "completed": { "type": "boolean" }
  },
  "additionalProperties": false
}

Reference these schemas in the service configuration:

{
  "services": [
    {
      "id": "todos",
      "path": "/todos",
      "module": "./src/services/todos.js",
      "schemas": {
        "create": "./schemas/todo-create.json",
        "update": "./schemas/todo-update.json"
      }
    }
  ]
}

Platformatic will now reject malformed payloads with a clear 400 response, saving you from writing repetitive validation code.

Step 3: Secure the API with JWT

Create an authentication plugin that verifies a JWT token and attaches the decoded payload to request.user. The plugin uses the popular jsonwebtoken library.

// src/plugins/jwt-auth.js
const jwt = require('jsonwebtoken');

module.exports = async function (app, { secret }) {
  app.addHook('onRequest', async (request, reply) => {
    const authHeader = request.headers['authorization'];
    if (!authHeader) {
      reply.code(401);
      return { error: 'Missing Authorization header' };
    }
    const token = authHeader.split(' ')[1];
    try {
      const payload = jwt.verify(token, secret);
      request.user = payload;
    } catch (err) {
      reply.code(401);
      return { error: 'Invalid token' };
    }
  });
};

Register the plugin globally and provide the secret via environment variables:

{
  "plugins": [
    {
      "module": "./src/plugins/jwt-auth.js",
      "options": { "secret": "${JWT_SECRET}" }
    }
  ]
}

Now every request to /todos is automatically protected, and the service code can safely assume request.user.id exists.

Real‑World Use Cases

Microservice Architecture – Platformatic’s modular service definition fits perfectly into a microservice ecosystem. Each domain (users, orders, inventory) lives in its own repository, shares a common plugin library for logging and auth, and publishes its OpenAPI spec to an API gateway.

Rapid Prototyping – Start with a platformatic init, drop a few SQL tables, and let the framework generate CRUD endpoints instantly. This accelerates MVP development and allows product teams to validate ideas without a full‑stack engineering effort.

Enterprise‑Grade APIs – With built‑in OpenAPI generation, you can feed the spec into tools like Swagger UI, Postman, or Kong. Combined with automatic request validation, you get a contract‑first development experience that reduces runtime bugs and improves client‑side integration.

Pro Tips & Best Practices

Tip 1: Keep your platformatic.json DRY by extracting shared plugin configurations into a separate plugins/common.json file and using the $ref keyword. This makes updates across multiple services a single‑line change.

Tip 2: Enable fastify-compress as a global plugin to automatically gzip responses. It reduces bandwidth and improves perceived performance for API consumers.

Tip 3: Use the CLI’s platformatic test command to run integration tests against a temporary in‑memory PostgreSQL instance. This guarantees your SQL queries work in CI without managing external resources.

Testing and Deployment

Platformatic ships with a built‑in testing harness that spins up the server in a sandboxed environment. Write tests using your favorite framework (Jest, Mocha, or Ava) and import the generated OpenAPI spec to validate request/response shapes.

// test/todos.test.js
const { build } = require('platformatic');
const request = require('supertest');

let app;

beforeAll(async () => {
  app = await build({ config: './platformatic.json' });
  await app.start();
});

afterAll(async () => {
  await app.close();
});

test('GET /todos returns an empty array for a new user', async () => {
  const token = generateJwt({ id: 1, email: 'test@example.com' });
  const res = await request(app.server)
    .get('/todos')
    .set('Authorization', `Bearer ${token}`);
  expect(res.statusCode).toBe(200);
  expect(res.body).toEqual([]);
});

For deployment, containerize the app with a lightweight node:18-alpine image. The CLI can produce a production build that strips dev dependencies and pre‑compiles the OpenAPI spec, resulting in a fast start‑up time.

# Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build   # runs platformatic build

FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
RUN npm ci --production
CMD ["node", "dist/index.js"]

Deploy the container to any orchestration platform—Kubernetes, AWS ECS, or Fly.io. Leverage Platformatic’s health‑check endpoint (/status) for liveness and readiness probes.

Extending Platformatic with Custom Middleware

While the built‑in plugins cover most use cases, you may need bespoke functionality such as rate limiting, feature flags, or request tracing. Create a custom plugin that registers Fastify hooks or routes, then reference it in the config. Because plugins receive the same Fastify instance, you can tap into the full Fastify ecosystem.

// src/plugins/rate-limit.js
module.exports = async function (app, { max }) {
  const rateLimiter = require('fastify-rate-limit');
  await app.register(rateLimiter, { max, timeWindow: '1 minute' });
};

Enable the plugin globally with a per‑environment limit:

{
  "plugins": [
    {
      "module": "./src/plugins/rate-limit.js",
      "options": { "max": "${RATE_LIMIT_MAX}" }
    }
  ]
}

Monitoring and Observability

Platformatic integrates smoothly with Prometheus and Grafana. Register the fastify-metrics plugin to expose a /metrics endpoint that reports request latency, error rates, and database query timings. Pair this with a lightweight log shipper (e.g., Fluent Bit) to centralize logs across services.

// src/plugins/metrics.js
module.exports = async function (app) {
  await app.register(require('fastify-metrics'), {
    endpoint: '/metrics',
    prefix: 'platformatic_'
  });
};

With metrics in place, you can set up alerts for spikes in 5xx errors or unusually high query latency—critical signals for maintaining SLA compliance.

Conclusion

Share this article