Skip to main content
CoreSDK
Getting Started

TypeScript Quickstart

Add CoreSDK to an Express, Fastify, or Next.js service.

Phase 2 — not yet available. The TypeScript SDK (@coresdk/sdk) ships in Phase 2. The code below reflects the planned API and the package is not yet published.

TypeScript Quickstart

Prerequisites

  • Node.js 18+
  • TypeScript 5+
  • A running identity provider that exposes a JWKS endpoint (Auth0, Clerk, Keycloak, etc.)

Install

npm install @coresdk/sdk

# or with pnpm / yarn
pnpm add @coresdk/sdk
yarn add @coresdk/sdk

Initialize the SDK

Create the SDK once and export it — import it wherever you need middleware or type-safe user access:

// lib/sdk.ts
import { CoreSDK } from "@coresdk/sdk";

export const sdk = new CoreSDK({
  tenant:       "acme",
  jwksUrl:      "https://your-idp.com/.well-known/jwks.json",
  policyDir:    "./policies",
  otelEndpoint: "http://localhost:4317", // optional
});
OptionDescription
tenantYour tenant slug — stamped on every span and error response
jwksUrlJWKS endpoint used to verify incoming JWTs
policyDirDirectory of .rego policy files (watched for live reload)
otelEndpointOTLP gRPC endpoint for traces and metrics (optional)

Full Express example

A complete Express application with typed request objects:

// server.ts
import express from "express";
import { sdk } from "./lib/sdk";
import type { CoreSDKUser, CoreSDKTenant } from "@coresdk/sdk";

// Augment the Express Request type so req.user and req.tenant are typed everywhere
declare global {
  namespace Express {
    interface Request {
      user:   CoreSDKUser;
      tenant: CoreSDKTenant;
    }
  }
}

const app = express();
app.use(express.json());

// CoreSDKMiddleware validates JWTs, resolves tenant context, and populates
// req.user and req.tenant for every request before route handlers run.
app.use(CoreSDKMiddleware);

// GET /api/orders — requires the "orders:read" policy action
app.get(
  "/api/orders",
  require_role("orders:read"),
  async (req, res) => {
    // req.user and req.tenant are fully typed at this point
    const orders = await db.orders.forUser(req.user.id, req.tenant.id);
    res.json(orders);
  }
);

// GET /api/orders/:id
app.get(
  "/api/orders/:id",
  require_role("orders:read"),
  async (req, res) => {
    const order = await db.orders.get(req.params.id, req.tenant.id);
    if (!order) return res.status(404).json({ error: "not found" });
    res.json(order);
  }
);

// POST /api/orders — requires "orders:write"
app.post(
  "/api/orders",
  require_role("orders:write"),
  async (req, res) => {
    const order = await db.orders.create({
      description: req.body.description,
      userId:      req.user.id,
      tenantId:    req.tenant.id,
    });
    res.status(201).json(order);
  }
);

// DELETE /api/orders/:id — requires "orders:delete"
app.delete(
  "/api/orders/:id",
  require_role("orders:delete"),
  async (req, res) => {
    await db.orders.delete(req.params.id, req.tenant.id);
    res.status(204).send();
  }
);

// Profile endpoint — auth required but no policy action enforced
app.get("/me", CoreSDKMiddleware, async (req, res) => {
  res.json({
    id:       req.user.id,
    email:    req.user.email,
    role:     req.user.role,
    tenantId: req.user.tenantId,
    claims:   req.user.rawClaims,
  });
});

app.listen(3000, () => {
  console.log("listening on http://localhost:3000");
});

TypeScript types

@coresdk/sdk exports full TypeScript types. You can import them for use in service layers, tests, and other files that don't import Express directly:

import type { CoreSDKUser, CoreSDKTenant } from "@coresdk/sdk";

// CoreSDKUser — populated on req.user by the middleware
interface CoreSDKUser {
  id:        string;                    // stable user ID (JWT sub claim)
  email:     string;                    // from the identity provider
  role:      string;                    // resolved from tenant membership
  tenantId:  string;                    // tenant the user belongs to
  rawClaims: Record<string, unknown>;   // full JWT claims object
}

// CoreSDKTenant — populated on req.tenant by the middleware
interface CoreSDKTenant {
  id:       string;
  name:     string;
  plan:     "free" | "team" | "business" | "enterprise";
  metadata: Record<string, unknown>;
}

require_role() options

// Require a single action
app.get("/api/orders", require_role("orders:read"), handler);

// Require any one of several actions (OR semantics)
app.get("/api/orders", sdk.requireAny(["orders:read", "orders:admin"]), handler);

// Pass resource context to the Rego policy for attribute-based decisions
app.get(
  "/api/orders/:id",
  require_role("orders:read", {
    resource: (req) => ({ id: req.params.id }),
  }),
  handler
);

Fastify example

import Fastify from "fastify";
import { coreSDKPlugin } from "@coresdk/sdk/fastify";

const fastify = Fastify({ logger: true });

await fastify.register(coreSDKPlugin, {
  tenant:    "acme",
  jwksUrl:   "https://your-idp.com/.well-known/jwks.json",
  policyDir: "./policies",
});

fastify.get(
  "/api/orders",
  { preHandler: fastify.corerequire_role("orders:read") },
  async (request, reply) => {
    // request.user and request.tenant are typed by the plugin
    return db.orders.forUser(request.user.id, request.tenant.id);
  }
);

fastify.post(
  "/api/orders",
  { preHandler: fastify.corerequire_role("orders:write") },
  async (request, reply) => {
    const order = await db.orders.create({
      ...(request.body as { description: string }),
      userId:   request.user.id,
      tenantId: request.tenant.id,
    });
    reply.status(201).send(order);
  }
);

await fastify.listen({ port: 3000, host: "0.0.0.0" });

Next.js App Router example

Use withCoreSDK to wrap individual Route Handlers in the App Router:

// app/api/orders/route.ts
import { withCoreSDK } from "@coresdk/sdk/next";
import { sdk } from "@/lib/sdk";

export const GET = withCoreSDK(
  sdk,
  { action: "orders:read" },
  async (req, { user, tenant }) => {
    const orders = await db.orders.forUser(user.id, tenant.id);
    return Response.json(orders);
  }
);

export const POST = withCoreSDK(
  sdk,
  { action: "orders:write" },
  async (req, { user, tenant }) => {
    const body = await req.json();
    const order = await db.orders.create({
      description: body.description,
      userId:      user.id,
      tenantId:    tenant.id,
    });
    return Response.json(order, { status: 201 });
  }
);

For middleware-level protection (protecting entire route segments), add CoreSDK to middleware.ts:

// middleware.ts
import { createNextMiddleware } from "@coresdk/sdk/next";
import { sdk } from "./lib/sdk";

// Runs on every request matching the config.matcher pattern
export const middleware = createNextMiddleware(sdk);

export const config = {
  matcher: ["/api/:path*"],
};

Run locally

# Install dependencies
npm install

# Start with ts-node (development)
npx ts-node server.ts

# Or compile and run
npx tsc && node dist/server.js

# Test with a valid JWT (replace with a real token from your IdP)
curl http://localhost:3000/api/orders \
  -H "Authorization: Bearer <your-jwt>"

# Without a token — expect 401
curl http://localhost:3000/api/orders

# Check your identity
curl http://localhost:3000/me \
  -H "Authorization: Bearer <your-jwt>"

Next steps

On this page