iocraft

A lightweight IOC container for Vue 3 using the Composition API.
2,515 2
Install
npm install iocraft
Latest Version:0.4.0
Vue:3
License:MIT
Last Updated:Sep 13, 2026
Links: GitHub  ·  npm
Maintainer: istiuak
iocraft

npm version license typescript

iocraft is a dependency injection container for Vue 3 built on the Composition API.

Table of Contents

Installation

npm install iocraft

TypeScript Configuration

iocraft uses decorators, so you must enable experimental decorators in your TypeScript config.

Update your tsconfig.app.json (typical Vue / Vite setup):

{
  "extends": "@vue/tsconfig/tsconfig.dom.json",
  "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
  "exclude": ["src/**/__tests__/*"],
  "compilerOptions": {
    "noUncheckedIndexedAccess": true,
    "paths": {
      "@/*": ["./src/*"]
    },

    "experimentalDecorators": true, // Add this

    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo"
  }
}

Setup

import { createApp } from "vue";
import { iocraft } from "iocraft";
import App from "./App.vue";

const app = createApp(App);
app.use(iocraft);
app.mount("#app");

With options:

app.use(iocraft, {
  router, // enables the built-in Nav service
  eagerLoad: [AuthService], // Eagerly initiate services
});

Services

Decorate a class with @attach() to register it with the DI system.

import { ref, computed } from "vue";
import { attach } from "iocraft";

@attach()
export class CounterService {
  count = ref(0);
  double = computed(() => this.count.value * 2);

  increment() {
    this.count.value++;
  }

  reset() {
    this.count.value = 0;
  }
}

@attach() assigns a unique symbol token to the class that the registry uses for lookup. Without it, iocraft cannot resolve the service and will throw.

Obtaining Services

obtain

obtain Resolves a singleton from the registry. Initiates the class once on first call, and reuses that instance for the lifetime of the app. It returns a facade of that class instance, which is a thin wrapper that proxies each property through getters/setters linked to the original instance, preserving reactivity even when destructuring.

<script setup lang="ts">
import { obtain } from "iocraft";
import { CounterService } from "./services";

const counter = obtain(CounterService);
const { count, increment, reset } = obtain(CounterService);
</script>

<template>
  <p>Count: {{ count }} — Double: {{ counter.double }}</p>
  <button @click="increment">+1</button>
  <button @click="reset">Reset</button>
</template>

obtain.raw

obtain.raw Resolves a singleton from the root registry, just like obtain, but returns the actual class instance with no facade applied.

const auth = obtain.raw(AuthService);

obtain.instance

obtain.instance Returns a fresh facade of the given service on each call.

<script setup lang="ts">
import { obtain } from "iocraft";
import { FormService } from "./services";

const { values, errors, submit, reset } = obtain.instance(FormService);
</script>

<template>
  <form @submit.prevent="submit">
    <input v-model="values.email" />
    <span v-if="errors.email">{{ errors.email }}</span>
    <button type="submit">Submit</button>
  </form>
</template>

Context (Scoped) Services

Provide a service from a parent component and inject it into any descendant. Uses Vue's provide / inject under the hood.

Parent:

<script setup lang="ts">
import { expose, obtain } from "iocraft";
import { CartService } from "./services";

const cartService = obtain.instance(CartService);
expose(cartService);
</script>

Child:

<script setup lang="ts">
import { obtain } from "iocraft";
import { CartService } from "./services";

const cart = obtain.exposed(CartService); // CartService | undefined
</script>

Router Integration

iocraft provides a built-in Nav service that exposes all routing features as a plain injectable service — no need to call useRouter() and useRoute() separately. To enable it, pass your router when registering the plugin:

app.use(iocraft, { router });

Once registered, Nav is available as a global singleton anywhere in your app:

<script setup lang="ts">
import { obtain, Nav } from "iocraft";

const nav = obtain(Nav);
</script>

<template>
  <p>
    Path: <code>{{ nav.path }}</code>
  </p>
  <p>
    Name: <code>{{ nav.name?.toString() ?? "-" }}</code>
  </p>
  <p>
    Full path: <code>{{ nav.fullPath }}</code>
  </p>
  <button @click="nav.push('/home')">Home</button>
  <button @click="nav.back()">Back</button>
  <button @click="nav.forward()">Forward</button>
</template>

Nav API

Reactive route properties: path · name · params · query · hash · fullPath · meta · matched · redirectedFrom · currentRoute

Router state: options · listening (readable and writable)

Navigation: push(to) · replace(to) · go(delta) · back() · forward()

Route resolution: resolve(to)

Route registry: addRoute() · removeRoute() · getRoutes() · hasRoute() · clearRoutes()

Guards: beforeEach() · beforeResolve() · afterEach() · onError()

Lifecycle: isReady()

License

MIT · istiuak-0

Related Packages

@vue-ioc/core

IoC and DI for Vue powered by InversifyJS and inspired by Angular @Module syntac...

786 44
@nonnajs/vue

Vue bindings for @nonnajs/di - a component plus useInjection()/useOptionalInject...

439 2
@iappx/vue-facing-di

Class-based Vue 3 components with constructor dependency injection powered by ts...

605 0
@iappx/pinia-di

Class-based Pinia stores with constructor dependency injection powered by tsyrin...

0 0
nuxt-ioc

Nuxt IOC implementation

5,235 7

Version History

Version Released Vue License
0.4.0 ^3.0.0 MIT
0.3.0 ^3.0.0 MIT
0.2.11 ^3.0.0 MIT
0.2.10 ^3.0.0 MIT
0.2.9 ^3.0.0 MIT