Extending Jetpath: Plugins

Plugins are the primary way to extend Jetpath’s core functionality, promote code reuse, and encapsulate complex or shared logic, such as authentication, database interactions, file handling, logging, or connections to third-party services.


What are Plugins?

Think of plugins as self-contained modules that can:

  1. Initialize Resources: Set up database connections, configure API clients, read configuration, etc., when the application starts.
  2. Expose Functionality: Add new methods and properties to the ctx.plugins object, making them easily accessible within your middleware and route handlers.
  3. Manage Dependencies: Encapsulate dependencies needed by the plugin’s functionality (as discussed in Dependency Injection).

Using Plugins

Integrating existing plugins (whether official Jetpath plugins or community-created ones) is straightforward.

1. Installation

Install the plugin package using your preferred package manager:

# Example installing an official file upload plugin
npm install @jetpath/plugin-busboy
# or
bun add @jetpath/plugin-busboy
# or add via import map/URL for Deno

2. Registration

Instantiate the plugin (if necessary, passing configuration options) and register it with your Jetpath application instance using app.use(). Registration typically happens in your main server file (server.ts).

// server.ts
import { Jetpath } from "jetpath";
// Assuming jetbusboy is the exported plugin factory/instance
import { jetbusboy } from "@jetpath/plugin-busboy";
import { createAuthPlugin } from "./plugins/authPlugin"; // Your custom auth plugin

const app = new Jetpath({ source: "./src" });

// Instantiate and register plugins // Official plugin for multipart/form-data handling app.use(jetbusboy);

// Custom authentication plugin (example) const authPlugin = createAuthPlugin({ /* options like JWT secret */ }); app.use(authPlugin);

app.listen();

[cite: Registration pattern shown in tests/app.jet.ts]

Important: Plugins are generally executed/initialized in the order they are registered with app.use().

3. Accessing Plugin Functionality

Once registered, the methods and properties returned by the plugin’s executor function become available on the ctx.plugins object within middleware and route handlers.

// In a route handler or middleware
import type { JetFunc, JetMiddleware } from "jetpath";
// Import types exposed by plugins if available
import type { JetBusBoyType } from "@jetpath/plugin-busboy";
import type { AuthPluginAPI } from "./plugins/authPlugin";

// Use generics to type ctx.plugins type HandlerPlugins = [JetBusBoyType, AuthPluginAPI];

export const POST_upload: JetFunc<{}, HandlerPlugins> = async (ctx) => { // Access file upload functionality from jetbusboy plugin const formData = await ctx.plugins.formData(ctx); const image = formData.image; // … process image …

ctx.<span class="hljs-title function_">send</span>({ <span class="hljs-attr">message</span>: <span class="hljs-string">&quot;Upload processed&quot;</span> });

};

export const GET_profile: JetFunc<{}, HandlerPlugins> = (ctx) => { // Access auth functionality from authPlugin const authResult = ctx.plugins.verifyAuth(ctx); // Example method name if (!authResult.authenticated) { ctx.throw(401, "Not authenticated"); } ctx.send({ user: authResult.user }); };

[cite: Usage pattern ctx.plugins.methodName() shown in tests/app.jet.ts]


Creating Plugins

Creating your own plugins allows you to structure reusable logic cleanly.

The JetPlugin Class

Jetpath provides a JetPlugin class (or a similar constructor pattern) to structure your plugin.

import { JetPlugin } from "jetpath";
import type { JetContext } from "jetpath"; // For typing ctx if needed

// Define the interface for the API your plugin will expose on ctx.plugins interface MyPluginAPI { doSomething: (input: string) => string; // Add other methods/properties }

// Define options your plugin might accept interface MyPluginOptions { prefix?: string; }

export function createMyPlugin(options: MyPluginOptions = {}): JetPlugin { const prefix = options.prefix || "DEFAULT";

// Instantiate plugin return new JetPlugin({ // The executor function runs when app.use() is called async executor(/* Optional args like app instance might be passed */): Promise<MyPluginAPI> { console.log(Initializing MyPlugin with prefix: <span class="hljs-subst">${prefix}</span>);

  <span class="hljs-comment">// === Perform Initialization ===</span>
  <span class="hljs-comment">// e.g., connect to a service, load config</span>
  <span class="hljs-comment">// const externalClient = await connectToService();</span>

  <span class="hljs-comment">// === Return the Plugin&#x27;s Public API ===</span>
  <span class="hljs-comment">// These methods become available on ctx.plugins</span>
  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">doSomething</span>: (<span class="hljs-attr">input</span>: <span class="hljs-built_in">string</span>): <span class="hljs-function"><span class="hljs-params">string</span> =&gt;</span> {
      <span class="hljs-comment">// This function has access to &#x27;prefix&#x27; and &#x27;externalClient&#x27;</span>
      <span class="hljs-comment">// via closure scope.</span>
      <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&quot;MyPlugin doing something...&quot;</span>);
      <span class="hljs-keyword">return</span> <span class="hljs-string">`<span class="hljs-subst">${prefix}</span>: Processed <span class="hljs-subst">${input}</span>`</span>;
      <span class="hljs-comment">// Example using initialized client:</span>
      <span class="hljs-comment">// return externalClient.process(input);</span>
    },
    <span class="hljs-comment">// Add other methods...</span>
  };
}

}); }

The executor Function

Example: Simplified Auth Plugin Structure

This mirrors the authPlugin structure seen in tests/app.jet.ts.

import { JetPlugin } from "jetpath";
import type { JetContext } from "jetpath";

// Define the API exposed by this plugin export interface AuthPluginAPI { verifyAuth: (ctx: JetContext) => { authenticated: boolean; user?: any; message?: string }; isAdmin: (ctx: JetContext) => boolean; }

// Define configuration options interface AuthPluginOptions { jwtSecret: string; adminApiKey: string; }

export function createAuthPlugin(options: AuthPluginOptions): JetPlugin { // Dependencies are configured here and accessible within the executor's returned methods const JWT_SECRET = options.jwtSecret; const ADMIN_API_KEY = options.adminApiKey; // In-memory store or DB connection could be initialized here

return new JetPlugin({ executor(): AuthPluginAPI { // Return the methods that handlers will call via ctx.plugins return { verifyAuth(ctx: JetContext) { const authHeader = ctx.get("authorization"); // … logic to validate token using JWT_SECRET … if (/* valid token */) { // const user = findUserFromToken(…); return { authenticated: true, user: { id: '…', role: '…' } }; } return { authenticated: false, message: "Invalid token" }; },

    <span class="hljs-title function_">isAdmin</span>(<span class="hljs-params"><span class="hljs-attr">ctx</span>: <span class="hljs-title class_">JetContext</span></span>) {
      <span class="hljs-keyword">if</span> (ctx.<span class="hljs-title function_">get</span>(<span class="hljs-string">&quot;x-admin-key&quot;</span>) === <span class="hljs-variable constant_">ADMIN_API_KEY</span>) {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
      }
      <span class="hljs-keyword">const</span> auth = <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">verifyAuth</span>(ctx); <span class="hljs-comment">// Can call other plugin methods</span>
      <span class="hljs-keyword">return</span> auth.<span class="hljs-property">authenticated</span> &amp;&amp; auth.<span class="hljs-property">user</span>?.<span class="hljs-property">role</span> === <span class="hljs-string">&quot;admin&quot;</span>;
    }
  };
}

}); }

[cite: Based on authPlugin structure in tests/app.jet.ts]


Plugin Lifecycle


Best Practices


Next Steps