miqro · @miqro/core · @miqro/parser · @miqro/query · @miqro/jsx · @miqro/jsx-dom · @miqro/jsx-node · @miqro/request · @miqro/runner · @miqro/test · @miqro/test-http

@miqro/core

promise based http router with TypeScript inference.

Router

import { Router } from "@miqro/core";

const router = new Router();

router.use(handler);                          // all methods, all paths
router.use(handler, "/path");                 // all methods, /path
router.use(handler, "/path", "GET");          // GET /path
router.get("/path", handler);                 // GET /path
router.catch(errorHandler);                   // error handler

await router.run(req, res);                   // returns true if no response sent

handler return values:

return false;         // stop chain, no response sent
return true;          // continue chain
return undefined;     // continue chain
return { ... };       // pushed to req.results, continue chain

App

http.createServer with router built in.

import { App } from "@miqro/core";

const app = new App({
  loggerFactory: (uuid, req) => logger,
  onUpgrade: (req, socket, head) => { }   // WebSocket upgrade
});

app.use(router);
await app.listen("3000");
await app.close();

APIRoute

complete endpoint declaration.

import { APIRoute, JSONParser } from "@miqro/core";

export default {
  name: "create post",
  description: "creates a post",
  path: "/posts",
  method: "POST",
  policy: {
    groups: ["editor"],
    groupPolicy: "at_least_one"
  },
  middleware: [JSONParser()],
  request: {
    body: {
      title: "string",
      content: "string",
      tags: "string[]?"
    },
    query: {
      draft: "boolean?"
    }
  },
  response: {
    status: [200, 400],
    body: {
      id: "number"
    }
  },
  handler: async (req, res) => {
    //req.body.title // is coerced to string
    return res.json({ id: 1 });
  }
} as APIRoute;

APIRoute with TypeScript inference (experimental)

complete endpoint declaration.

import { defineRoute, JSONParser } from "@miqro/core";

export default defineRoute({
  name: "create post",
  description: "creates a post",
  path: "/posts",
  method: "POST",
  policy: {
    groups: ["editor"],
    groupPolicy: "at_least_one"
  },
  middleware: [JSONParser()],
  request: {
    body: {
      title: "string",
      content: "string",
      tags: "string[]?"
    },
    query: {
      draft: "boolean?"
    }
  },
  response: {
    status: [200, 400],
    body: {
      id: "number"
    }
  },
  handler: async (req, res) => {
    //req.body.title // is coerced and typed string
    return res.json({ id: 1 });
  }
});

method: "use" matches all methods — use for middleware:

export default {
  path: "/admin",
  method: "use",
  handler: adminRouter
} as APIRoute;

middleware

JSONParser

parses application/json body into req.body.

import { JSONParser } from "@miqro/core";
router.use(JSONParser());

URLEncodedParser

parses application/x-www-form-urlencoded into req.body.

import { URLEncodedParser } from "@miqro/core";
router.use(URLEncodedParser());

ReadBuffer

reads raw body into req.buffer.

import { ReadBuffer } from "@miqro/core";
router.use(ReadBuffer());

TextParser

reads body as string into req.body.

import { TextParser } from "@miqro/core";
router.use(TextParser());

CORS

import { CORS } from "@miqro/core";

router.use(CORS({
  origins: ["https://myapp.com"],
  methods: "GET,POST,PUT,DELETE"
}));

without options reads CORS_ORIGINS, CORS_METHODS env vars. default: all origins.

invalid origins throw BadRequestError.

SessionHandler

verifies token and sets req.session.

import { SessionHandler } from "@miqro/core";

router.use(SessionHandler({
  verify: async ({ token }) => {
    // return session or null
  }
}));

token location via env vars: TOKEN_VERIFY_LOCATION (header|query|cookie), TOKEN_HEADER, TOKEN_QUERY, TOKEN_COOKIE.

or via remote endpoint: TOKEN_VERIFY_ENDPOINT.

GroupPolicyHandler

restricts access by req.session.groups.

import { GroupPolicyHandler } from "@miqro/core";

router.use(GroupPolicyHandler({
  groups: ["admin", "editor"],
  groupPolicy: "at_least_one"   // or "all"
}));

throws ForbiddenError if policy not met.

ParseRequest

validates req.body, req.query, req.params, req.headers against schema.

import { ParseRequest } from "@miqro/core";

router.use(ParseRequest({
  body: { title: "string", count: "number?" },
  query: { page: "number?" },
  params: { id: "integer" }
}));

throws BadRequestError if validation fails.

errors

import { 
  BadRequestError,      // 400
  UnAuthorizedError,    // 401
  ForbiddenError,       // 403
  NotFoundError,        // 404
  MethodNotImplementedError  // 501
} from "@miqro/core";

// throw from any handler
throw new BadRequestError("invalid input");
throw new UnAuthorizedError();
throw new ForbiddenError();

caught by router.catch() handlers or the default error handler.

Logger

import { Logger, ConsoleTransport, FileTransport } from "@miqro/core";

const logger = new Logger("MY_SERVICE", "info", {
  transports: [ConsoleTransport(), FileTransport("server.log")]
});

logger.error("message %s", value);
logger.warn("...");
logger.info("...");
logger.debug("...");
logger.trace("...");

level override via env: LOG_LEVEL=debug, LOG_LEVEL_MY_SERVICE=trace.

Request

import { Request } from "@miqro/core";

// all properties set before handlers run
req.path          // normalized pathname
req.query         // { [key]: string | string[] } — Object.create(null)
req.params        // { [key]: string } — Object.create(null)
req.cookies       // { [name]: string } — Object.create(null)
req.uuid          // randomUUID()
req.startMS       // Date.now() at request start
req.logger        // per-request logger
req.results       // []
req.body          // set by body parser middleware
req.buffer        // set by ReadBuffer
req.session       // set by SessionHandler

Static

serve static files.

import { Static } from "@miqro/core";

router.use(new Static({
  directory: "./public",
  autoIndex: true
}));

Proxy

proxy requests to another server.

import { Proxy } from "@miqro/core";

router.use(new Proxy({
  url: "http://backend:3001"
}));