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 { JetRoute, 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: JetRoute<{}, 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: JetRoute<{}, HandlerPlugins> = (ctx) => { // Access auth functionality from authPlugin const authResult = ctx.plugins.verifyAuth(ctx); // Example method name if (!authResult.authenticated) { ctx.send("Not authenticated", 402); } 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 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 ({ executor(): AuthPluginAPI { // Return the methods that handlers will call via ctx.plugins return {

    <span class="hljs-title function_">verifyAuth</span>(<span class="hljs-params"><span class="hljs-attr">this</span>: <span class="hljs-title class_">JetContext</span></span>) {
      <span class="hljs-comment">// ? the this here is the ctx of the api request, hence you have access to the entire context;</span>
      <span class="hljs-keyword">const</span> authHeader = <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">get</span>(<span class="hljs-string">&quot;authorization&quot;</span>);
      <span class="hljs-comment">// ... logic to validate token using JWT_SECRET ...</span>
      <span class="hljs-keyword">if</span> (<span class="hljs-comment">/* valid token */</span>) {
        <span class="hljs-comment">// const user = findUserFromToken(...);</span>
        <span class="hljs-keyword">return</span> { <span class="hljs-attr">authenticated</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">user</span>: { <span class="hljs-attr">id</span>: <span class="hljs-string">&#x27;...&#x27;</span>, <span class="hljs-attr">role</span>: <span class="hljs-string">&#x27;...&#x27;</span> } };
      }
      <span class="hljs-keyword">return</span> { <span class="hljs-attr">authenticated</span>: <span class="hljs-literal">false</span>, <span class="hljs-attr">message</span>: <span class="hljs-string">&quot;Invalid token&quot;</span> };
    },

    <span class="hljs-title function_">isAdmin</span>(<span class="hljs-params"><span class="hljs-attr">this</span>: <span class="hljs-title class_">JetContext</span></span>) {
      <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">this</span>.<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