Skip to content

Connecting a frontend to the NPL API

This guide explains how to use an OpenAPI specification to simplify the interface between a frontend and the NPL API. We will use the npl openapi command and the hey-api/openapi-ts library to generate type-safe TypeScript clients for your NPL applications.

Prerequisites

  • Noumena CLI installed
  • Node.js installed
  • Basic understanding of TypeScript

Using an example project

To use an example project with an example application containing a frontend and the client generation steps, run

npl init --project-dir my-project --template-url https://github.com/NoumenaDigital/npl-init/archive/refs/heads/frontend.zip

Generate an OpenAPI specification

NPL provides a built-in command to generate OpenAPI specifications from your protocols. From the root of the directory that was just created, run:

npl openapi

This command uses the structure.sourceDir and structure.outputDir parameters defined in npl.yml to locate your NPL source files and define the output directory for the generated OpenAPI files. You can also specify paths using the --source-dir and --output-dir command-line arguments.

The npl openapi command analyzes your NPL code and generates a comprehensive OpenAPI specification that includes:

  • All protocol endpoints
  • Request/response schemas
  • Authentication requirements
  • Parameter definitions
  • Engine root URL

For more details, see NPL Application API.

Install OpenAPI TypeScript generator

Navigate to the frontend module if needed:

cd frontend

Install the hey-api/openapi-ts library to generate type-safe clients:

npm install -D @hey-api/openapi-ts

Generate type-safe client

From the frontend folder, create a client generation script or run directly:

npx @hey-api/openapi-ts -i openapi/*-openapi.* -o ./src/clients/document

This generates:

  • TypeScript interfaces for all data models
  • Type-safe API client functions
  • Request/response type definitions

Note

If the NPL applications contains multiple packages, each corresponding openapi file will require an individual client generation. In this case, replace package-one with your package names twice in the text, and use

npx @hey-api/openapi-ts -i openapi/package-one-openapi.* -o ./src/clients/package-one

Using the generated client

Basic client setup

In your frontend, call to the api are made through the client object. The baseUrl must be configured to reach the NPL Engine.

import { client } from '../clients/document/client.gen' // "document" is the NPL package name; adjust name and path as needed

// For applications on NOUMENA Cloud
const baseUrl = "https://engine-TENANT_SLUG-APP_SLUG.noumenacloud.com"

// For local applications
// const baseUrl = "http://localhost:12000"

client.setConfig({ baseUrl })

For local development, the url would be http://localhost:12000 in ENGINE_DEV_MODE or http://localhost:12001 with a proxy. In the example project, baseUrl is set parametrically at construction of service DocumentService and controlled by environment variables.

Making API calls

The generated client provides type-safe methods for all your NPL endpoints:

// Import the function, adjust the function name and the path as needed
import { getDocumentList } from '../clients/document/sdk.gen'

// List all protocol instances
getDocumentList({
    client: api,
    ...withAuthorizationHeader()
})

// Create a new protocol instance
createDocument({
    body: {
        content: content,
        ['@parties']: {
            editor: {
                claims: {
                    email: [user.email]
                }
            },
            approver: {
                claims: {
                    email: [approverEmail]
                }
            }
        }
    },
    client: api,
    ...withAuthorizationHeader()
})

// Calling a permission
documentEdit({
    client: api,
    path: {
        id: documentId
    },
    body: {
        newContent: newContent
    },
    ...withAuthorizationHeader()
})

The withAuthorizationHeader() is a convenience function, defined in the non-generated example code (DocumentService service), that adds the necessary authorization header to the request:

export class DocumentService {
    // ...
    withAuthorizationHeader = () => {
        return {
            headers: { Authorization: `Bearer ${this.authProvider.token}` }
        }
    }
}

The API call functions are asynchronous and can be awaited

await documentEdit(/* */)
// continuation when the call is completed

and/or used with promise chaining

documentEdit(/* */).then(/* follow-up function call */)

Benefits of using generated clients

  1. Type Safety: Full TypeScript support with compile-time error checking
  2. Auto-completion: IDE support for all available endpoints and parameters
  3. Documentation: Generated JSDoc comments from OpenAPI specs
  4. Consistency: Standardized error handling and response formats
  5. Maintainability: Automatic updates when your NPL protocols change

Updating your client

When you update your NPL protocols, regenerate your client:

# 1. From the project root, regenerate OpenAPI spec
npl openapi

# 2. From the frontend module, regenerate client
cd frontend && npx @hey-api/openapi-ts -i openapi/*-openapi.* -o ./src/clients/document

# 3. Update your application code if needed

Run your full stack application

To run your full stack application locally, you need to start both the NPL Engine and the frontend development server.

Start the NPL Engine in DEV_MODE

From your project root directory, start the NPL Engine in development mode:

docker compose up -d --force-recreate --wait
npl deploy --clear

The engine will start on http://localhost:12000 and the embedded OIDC server on http://localhost:11000.

Build and run the frontend

  1. Navigate to your frontend directory:

    cd frontend
  2. Install dependencies if you haven't already:

    npm install
  3. Ensure the baseUrl points to the local engine service. In the snippet provided in the Basic client setup section, this amounts to setting

    const baseUrl = "http://localhost:12000"

    If you are using the example application created in section Using an example project, you do not have to change any code for local deployment, your frontend will be configured automatically to use http://localhost:12000 for the baseUrl (connection to the engine) and http://localhost:11000 for the URL of the OIDC (authentication), and you can proceed to the next step.

  4. Start the development server:

    npm run dev

    The frontend will typically start on http://localhost:5173 (or another port if 5173 is in use).

Verify the connection

Open your browser to the frontend URL (e.g., http://localhost:5173) and verify that:

  1. The frontend loads successfully
  2. You can log in with DEV MODE credentials (example: alice with password123)
  3. You can create and interact with protocol instances

Configuring for other deployment targets and login modes

The frontend example from the npl-init repository, frontend branch can be configured to connect to NPL Engines running locally or on NOUMENA Cloud. Similarly, it supports authenticating through keycloak, to a full OIDC client, or to the OIDC client embedded in the engine running in DEV MODE.

For more details, check out the README of the npl-init repository, frontend branch, also available in your repository if you have created your project from the frontend template (same command as above):

npl init --project-dir my-project --templateUrl https://github.com/NoumenaDigital/npl-init/archive/refs/heads/frontend.zip

Next steps

  • Deploy your frontend to Noumena Cloud
  • Connect any service to the NPL API
  • Listen & react to events from the NPL Runtime