MemoFSMemoFS
Self-Hosting

Node.js Server

Deploying @memofs/server on Node.js with the memofs-server CLI, custom node:http servers, Express, or Fastify.

@memofs/server provides a turnkey Node.js runtime for deploying MemoFS as a standalone background service or containerized workload (Docker, Fly.io, Railway, Render, VPS).

1. Using the memofs-server CLI Binary

The package includes an executable binary memofs-server that boots a hardened node:http server out of the box.

Installation

npm install -g @memofs/server

Starting the Server

# Boot the server (defaults to port 8787, in-memory store, auth off)
PORT=8787 memofs-server

Health Check

Verify the server is running:

curl http://127.0.0.1:8787/health
# {"ok":true,"name":"memofs-server","version":"0.1.0"}

Environment Variables

VariableDescriptionDefault
PORTHTTP listen port.8787
MEMOFS_SERVER_TOKENBearer token for authentication. Setting this automatically enables auth.undefined
MEMOFS_SERVER_REQUIRE_AUTHSet to "true" to enforce bearer token validation even without MEMOFS_SERVER_TOKEN."false"
MEMOFS_PROJECT_IDProject workspace ID scoping the runtime."self-host"

Production Hardening & DoS Protections

The memofs-server binary comes pre-configured with defensive network timeouts and resource caps:

  • Request Timeout (server.requestTimeout): 30,000ms (30s) to terminate slowloris hangs.
  • Headers Timeout (server.headersTimeout): 65,000ms (65s) to guard against slow header trickles.
  • Connection Limit (server.maxConnections): 100 concurrent TCP connections.
  • Payload Limit (MAX_BODY_BYTES): 1,048,576 bytes (1MB). Oversized payloads immediately receive 413 Payload Too Large.
  • Graceful Shutdown: Handles SIGTERM and SIGINT signals, draining existing connections before exiting.

2. Programmatic Node.js Deployment

To inject persistent storage adapters (e.g. SQLite, PostgreSQL, S3, or R2) or AI providers (OpenAI, Voyage AI), instantiate createHostedRuntime and route incoming requests through handleRuntimeRequest:

import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
import { createHostedRuntime, handleRuntimeRequest } from "@memofs/server";
import { createNodeFsMemoryStore } from "@memofs/core/node-fs";
import { createOpenAIEmbedder } from "@memofs/adapter-openai";
import { createVoyageReranker } from "@memofs/adapter-voyage";

// 1. Assemble the MemoFS runtime
const memofs = createHostedRuntime({
  store: createNodeFsMemoryStore({ rootDir: "./data" }),
  projectId: process.env.MEMOFS_PROJECT_ID ?? "prod-workspace",
  embedder: createOpenAIEmbedder({
    apiKey: process.env.OPENAI_API_KEY,
  }),
  reranker: createVoyageReranker({
    apiKey: process.env.VOYAGE_API_KEY,
  }),
});

// 2. Simple project mutex for the Concurrency Gate
const projectLock = new Map<string, Promise<unknown>>();
const concurrencyLayer = {
  acquire: async <T>(projectId: string, fn: () => Promise<T>): Promise<T> => {
    const prev = projectLock.get(projectId) ?? Promise.resolve();
    let resolveCurrent!: () => void;
    const current = new Promise<void>((r) => { resolveCurrent = r; });
    projectLock.set(projectId, current);
    try {
      await prev;
      return await fn();
    } finally {
      resolveCurrent();
    }
  },
};

// 3. Create HTTP server and bridge to handleRuntimeRequest
const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
  try {
    const method = req.method ?? "GET";
    const host = req.headers.host ?? "localhost:8787";
    const url = new URL(req.url ?? "/", `http://${host}`);

    // Read body buffer
    const chunks: Buffer[] = [];
    for await (const chunk of req) {
      chunks.push(chunk as Buffer);
    }
    const body = Buffer.concat(chunks);

    // Build Web Request
    const headers = new Headers();
    for (const [key, value] of Object.entries(req.headers)) {
      if (Array.isArray(value)) {
        for (const v of value) headers.append(key, v);
      } else if (value !== undefined) {
        headers.set(key, value);
      }
    }

    const webRequest = new Request(url.toString(), {
      method,
      headers,
      body: method === "GET" || method === "HEAD" ? undefined : body,
    });

    // Process with framework-free HTTP core
    const webResponse = await handleRuntimeRequest(webRequest, {
      runtime: memofs,
      concurrencyLayer,
      requireAuth: true,
      bearerToken: process.env.MEMOFS_SERVER_TOKEN,
      allowedOrigins: ["https://app.example.com"],
    });

    // Write back response
    res.statusCode = webResponse.status;
    webResponse.headers.forEach((val, key) => {
      res.setHeader(key, val);
    });

    const responseBuffer = Buffer.from(await webResponse.arrayBuffer());
    res.end(responseBuffer);

  } catch (error) {
    console.error("[server] Uncaught request error:", error);
    if (!res.headersSent) {
      res.writeHead(500, { "Content-Type": "text/plain" });
    }
    res.end("Internal Server Error");
  }
});

server.listen(8787, () => {
  console.log("MemoFS server listening on http://0.0.0.0:8787");
});

3. Securing Your Deployment

When exposing @memofs/server on public interfaces:

  1. Always Set a Bearer Token: Set MEMOFS_SERVER_TOKEN="secret-token". Clients must provide the header Authorization: Bearer secret-token.
  2. Restrict CORS Origins: Specify allowedOrigins: ["https://yourdomain.com"] to reject unauthorized cross-origin browser requests.
  3. Private Networks / VPCs: If running inside an internal Kubernetes cluster, Docker network, or behind a reverse proxy (e.g. NGINX, Cloudflare Tunnel), disable auth by setting requireAuth: false.

On this page