0.3.0 • Published 3 months ago

serverstruct v0.3.0

Weekly downloads
-
License
MIT
Repository
github
Last release
3 months ago

Serverstruct

⚡️ Typesafe and modular servers with Hono.

Serverstruct is a simple tool for building fast, modular and typesafe server applications with Hono and Hollywood DI.

Installation

Serverstruct requires you to install hono.

npm i serverstruct hono

To make use of dependency injection provided by serverstruct, also install hollywood-di.

npm i serverstruct hono hollywood-di

Usage

A simple app

import { createModule } from "serverstruct";

const app = createModule()
  .route((app) => {
    return app.get("/", (c) => c.text("Hello world!"));
  })
  .app();

export default app;

An app with submodules

import { createModule } from "serverstruct";

// users routes
const users = createModule().route((app) => {
  return app.get("/", (c) => c.text("users"));
});

// posts routes
const posts = createModule().route((app) => {
  return app.get("/", (c) => c.text("posts"));
});

const app = createModule()
  .submodules({ users, posts })
  .route((app, container, modules) => {
    return app
      .get("/", (c) => c.text("Hello world!"))
      .route("/users", modules.users)
      .route("/posts", modules.posts);
  })
  .app();

export default app;

Module

A module is a Hono app that may require dependencies or provide dependencies of it's own. A module may compose other submodules. A module with dependencies can only be used as a submodule to another module if that module satisfies it's dependencies.

import { createModule } from "serverstruct";

const auth = createModule().route((app) => {
  return app; // chain route handlers here
});

const users = createModule().route((app) => {
  return app; // chain route handlers here
});

const app = createModule()
  .submodules({ auth, users })
  .route((app, container, modules) => {
    return app.route("/auth", modules.auth).route("/users", modules.users);
  })
  .app();

Submodules are not automatically added to the Hono app, you will have to manually mount each route. This helps in preserving Hono's type inference through method chaining.

You may also pass a custom Hono app to createModule.

import { Hono } from "hono";
import { createModule } from "serverstruct";

const auth = createModule(new Hono().basePath("/auth")).route((app) => {
  return app; // chain route handlers here
});

const users = createModule(new Hono().basePath("/users")).route((app) => {
  return app; // chain route handlers here
});

const app = createModule()
  .submodules({ auth, users })
  .route((app, container, modules) => {
    return app.route("", modules.auth).route("", modules.users);
  })
  .app();

Dependency Injection

Serverstruct is designed to work with Hollywood DI. Modules can define their dependencies using use, and also register new tokens using provide.

A root container can also be passed to a module. If no container is explicitly provided the first call to provide creates a root container and futher calls to provide will inherit from it.

Use

Define module dependencies. The module can then only be used in a context that satisfies it's dependencies.

You can only call use once and only before calling provide.

import { Hollywood } from "hollywood-di";
import { createModule } from "serverstruct";

interface Counter {
  count: number;
  increment(): void;
}

const countModule = createModule()
  .use<{ counter: Counter }>()
  .route((app, container) => {
    return app.get("/", (c) => {
      container.counter.increment();
      return c.text(`Count is: ${container.counter.count}`);
    });
  });

class LinearCounter {
  public count = 0;
  public increment() {
    this.count++;
  }
}

// as a submodule
const app = createModule()
  .provide({ counter: LinearCounter })
  .submodules({ count: countModule })
  .route((app, _, modules) => {
    return app.route("", modules.count);
  })
  .app();

// or as the main app
const container = Hollywood.create({ counter: LinearCounter });
const app = countModule.app(container);

Calling Module.app() returns the Hono app, so if the module has dependencies (by calling use), a container that satisfies those dependencies must be provided as seen in the example above.

Provide

Provide creates a new child container. Registered tokens can then be used in the module and in futher calls to provide. See more about register tokens in Hollywood DI.

import { createModule } from "serverstruct";

class Foo {}
class Bar {
  constructor(public ctx: { foo: Foo }) {}
}

const module = createModule()
  .provide({
    foo: Foo,
    bar: Bar,
  })
  .module((app, container) => {
    return app;
  });

Incremental Adoption

Serverstruct can be added to an existing Hono app.

// modules/posts.ts
import { createModule } from "serverstruct";

export const postsModule = createModule().route((app) => {
  return app.get("/", (c) => {
    return c.text("posts");
  });
});

// main.ts
import { Hono } from "hono";
import { postsModule } from "./modules/posts";

const app = new Hono();

app.get("/", (c) => c.text("Hello world!"));
app.route("/posts", postsModule.app());

export default app;
0.3.0

3 months ago

0.2.0

11 months ago

0.1.1

2 years ago

0.1.0

2 years ago