Skip to main content

API Library Overview

The @elcto/api package provides unified REST, GraphQL, and WebSocket clients for Heimdall platform applications.

Installation

The library is already included in both platform/id and platform/backend. For new Next.js apps in the monorepo:

  1. Add the dependency to package.json:
{
"dependencies": {
"@elcto/api": "workspace:*"
}
}
  1. Add to next.config.ts:
const nextConfig: NextConfig = {
transpilePackages: ['@elcto/api', '@elcto/ui'],
};
  1. Run pnpm install

Config-first design

@elcto/api is config-first: the library itself reads no environment variables. Every transport (apiRequest, graphqlRequest, gql, createWebSocket, useWebSocket) and every route helper takes an ApiClientConfig as its first argument:

interface ApiClientConfig {
baseUrl: string; // e.g. "http://localhost:3000" (NO trailing /v1)
wsUrl?: string; // e.g. "ws://localhost:3000/v1/ws" (derived from baseUrl if absent)
systemApiKey?: string; // service-to-service / SSR auth
}

Each app builds this from its own src/lib/config.ts (which resolves HEIMDALL_* → NEXT_PUBLIC_* → localhost default) and exposes a getApiConfig() helper via @/lib/api. Pass getApiConfig() as the first argument everywhere:

import { getApiConfig, gql } from "@/lib/api";

const data = await gql<{ user: User }>(getApiConfig(), query, { id: "123" });

Quick Start

REST API

import { getApiConfig, apiRequest } from "@/lib/api";

// GET request
const response = await apiRequest<User>(getApiConfig(), "/v1/users/123");
if (response.data) {
console.log(response.data);
}

// POST request
const result = await apiRequest<void>(getApiConfig(), "/v1/users", {
method: "POST",
body: { name: "John" },
});

GraphQL

import { getApiConfig, gql, graphqlRequest } from "@/lib/api";

// Using gql helper (throws on error)
const data = await gql<{ user: User }>(getApiConfig(), `
query GetUser($id: ID!) {
user(id: $id) {
id
name
}
}
`, { id: "123" });

// Using raw request (returns errors)
const response = await graphqlRequest<{ user: User }>(getApiConfig(), query, variables);
if (response.errors) {
console.error(response.errors);
}

WebSocket (Client-side only)

import { getApiConfig, createWebSocket } from "@/lib/api";

const ws = createWebSocket(getApiConfig(), {
accessToken: session.accessToken,
autoReconnect: true,
});

ws.onMessage((data) => {
if (data.type === "accountBanned") {
// Handle ban
}
});

ws.send({ type: "ping" });
ws.close();

React Hook

import { getApiConfig, useWebSocket } from "@/lib/api";

function LiveUpdates() {
const { isConnected, lastMessage, send } = useWebSocket(getApiConfig(), {
accessToken: session.accessToken,
});

return (
<div>
<p>Status: {isConnected ? "Connected" : "Disconnected"}</p>
<button onClick={() => send({ type: "ping" })}>Ping</button>
</div>
);
}

Authentication

Server-side (REST & GraphQL)

When no accessToken is supplied, requests fall back to config.systemApiKey (the app populates this from its SYSTEM_API_KEY env var via getApiConfig()):

// Falls back to config.systemApiKey if set
const response = await apiRequest<User>(getApiConfig(), "/v1/users/123");

// Or provide a specific access token
const response = await apiRequest<User>(getApiConfig(), "/v1/users/123", {
accessToken: userAccessToken,
});

Priority: accessToken option > config.systemApiKey > no auth

Client-side (WebSocket)

WebSocket connections derive their URL from the config (config.wsUrl, falling back to config.baseUrl + /v1/ws) and take the access token via options:

const ws = createWebSocket(getApiConfig(), {
accessToken: session.accessToken,
});

Environment Variables

The library reads no environment variables itself. Each app's src/lib/config.ts resolves these and feeds them into getApiConfig():

VariableMaps toDescriptionDefault
HEIMDALL_API_URLNEXT_PUBLIC_API_URLconfig.baseUrlAPI base URLhttp://localhost:3000
HEIMDALL_WS_URLNEXT_PUBLIC_WS_URLconfig.wsUrlWebSocket URLws://localhost:3000/v1/ws
SYSTEM_API_KEYconfig.systemApiKeyServer-side API key (no public fallback)-

Package Structure

shared/api/
├── src/
│ ├── index.ts # Main exports
│ ├── client.ts # ApiClientConfig type
│ ├── clients/
│ │ ├── rest.ts # REST client
│ │ ├── graphql.ts # GraphQL client
│ │ ├── storage.ts # Storage client
│ │ └── websocket.ts # WebSocket client
│ ├── types/
│ │ ├── audit.ts # Audit types & constants
│ │ ├── common.ts # Common types (pagination, config)
│ │ ├── discord.ts # Discord integration types
│ │ ├── graphql.ts # GraphQL types
│ │ ├── integration.ts # Integration types
│ │ ├── platform.ts # Platform types
│ │ ├── rest.ts # REST types
│ │ ├── role.ts # Role types
│ │ ├── session.ts # Session types
│ │ ├── storage.ts # Storage types
│ │ ├── system-setting.ts # System setting types
│ │ ├── user.ts # User types
│ │ └── websocket.ts # WebSocket types & messages
│ ├── routes/
│ │ ├── audit.ts # Audit log routes
│ │ ├── permissions.ts # Permission management routes
│ │ ├── platforms.ts # Platform routes
│ │ ├── roles.ts # Role management routes
│ │ ├── sessions.ts # Session management routes
│ │ ├── settings.ts # System settings routes
│ │ └── users.ts # User API routes
│ ├── hooks/
│ │ └── useWebSocket.ts # React WebSocket hook
│ ├── constants/
│ │ ├── index.ts # Constants barrel
│ │ └── roles.ts # Role constants & helpers
│ └── errors/
│ └── index.ts # Error handler & codes

Next Steps