Type-Safe Backend for TypeScript

Dependency injection that your IDE understands. Compile-time validation. Zero runtime magic. Just functions and types.

$npm install @justscale/core @justscale/http
app.ts
const UserService = createService({
  inject: { db: DatabaseService },
  factory: ({ db }) => ({
    findById: (id) => db.query('SELECT * FROM users WHERE id = ?', [id]),
  }),
});

// TypeScript knows the full type of UserService
// Your IDE gives you autocomplete on db.query, findById, etc.

Why JustScale?

Built for developers who want their IDE to do the heavy lifting

Compile-Time Safety

All dependencies validated by TypeScript. Missing a service? You'll know before you run.

Zero Runtime Magic

No decorators, no reflect-metadata, no surprises. Just functions and types.

Transport Agnostic

Same services work across HTTP, CLI, WebSocket. Write once, expose anywhere.

Modular by Design

Features are composable units. Share them across projects, test them in isolation.

See It In Action

Watch how type-safe dependency injection works step by step

Define Your Service

Create a service with typed dependencies

Loading editor...
Step 1 of 3Hover over code to see types

Wire It Up

Controllers define routes. Bootstrap wires everything together.

controllers.tsTypeScript
app.tsTypeScript

What If You Forget?

TypeScript catches missing dependencies at compile time

app.ts
import { createClusterBuilder } from '@justscale/core' import { UserService } from './services' import { UserController } from './controllers' const built = createClusterBuilder() // Oops! Forgot to add UserService .add(UserController) .build()
TypeScript Error
Argument of type 'typeof UserController' is not assignable. Property 'UserService' is missing in the cluster context. UserController requires: { users: UserService } Cluster provides: { }

No runtime surprises. Fix it before you ship.

Now add middleware to protect and enhance your routes ↓

Middleware That Types

Each middleware adds to the context. TypeScript tracks it all.

Middleware Chain

Watch context accumulate through the chain

{res,params,}
base middleware guard checks
admin-routes.ts
import { Delete } from '@justscale/http';
import { parseAuth, loadProfile } from './middleware';
// Guard checks if user is admin
const isAdmin = (ctx: { user: User }) => ctx.user.role === 'admin';
const AdminController = createController('/admin', {
  routes: () => ({
    deleteUser: Delete('/users/:id')      .use(parseAuth)     // Adds: { user }      .use(loadProfile)   // Adds: { profile }      .guard(isAdmin)     // Checks: user.role      .handle(({ user, profile, params, res }) => {
        // TypeScript knows all context properties!
        res.json({ deleted: params.id });
      }),  }),
});

Don't Repeat Yourself

Bundle common middleware chains with .apply()

before
9 lines repeated
deleteUser: Delete('/users/:id')
  .use(parseAuth)
  .use(loadProfile)
  .guard(isAdmin)
  .handle(...),

updateUser: Put('/users/:id')
  .use(parseAuth)
  .use(loadProfile)
  .guard(isAdmin)
  .handle(...),

getStats: Get('/stats')
  .use(parseAuth)
  .use(loadProfile)
  .guard(isAdmin)
  .handle(...),
after
define once
// Bundle once
const adminOnly = bundle()
  .use(parseAuth)
  .use(loadProfile)
  .guard(isAdmin);

deleteUser: Delete('/users/:id')
  .apply(adminOnly).handle(...),

updateUser: Put('/users/:id')
  .apply(adminOnly).handle(...),

getStats: Get('/stats')
  .apply(adminOnly).handle(...),

Bundles preserve types. Context from parseAuth and loadProfile flows through to your handler.

Ready to Build?

Get started in minutes. Your types will thank you.

npm install @justscale/core @justscale/http