> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/medusajs/medusa/llms.txt
> Use this file to discover all available pages before exploring further.

# Dependency Injection

> Learn about Medusa's dependency injection system powered by Awilix, including service registration, resolution, and container patterns.

# Dependency Injection

Medusa uses **Awilix** as its dependency injection (DI) container to manage service dependencies, promote loose coupling, and enable testability. The container automatically resolves and injects dependencies when services are accessed.

## What is Dependency Injection?

Dependency Injection is a design pattern where objects receive their dependencies from an external source rather than creating them internally. This enables:

* **Loose coupling** - Services don't depend on concrete implementations
* **Testability** - Dependencies can be mocked or stubbed
* **Modularity** - Services can be swapped or extended
* **Lifecycle management** - Container controls object creation and disposal

<Note>
  Medusa's DI container is based on Awilix, a powerful IoC container for Node.js that supports constructor injection, lifetime management, and automatic resolution.
</Note>

## The Container

The Medusa container is an Awilix container that holds all registered services, modules, and dependencies:

```typescript theme={null}
import type { MedusaContainer } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"

function myFunction(container: MedusaContainer) {
  // Resolve a module service
  const productModule = container.resolve(Modules.PRODUCT)
  
  // Resolve a custom service
  const myService = container.resolve("myService")
  
  // Resolve with error handling
  const optionalService = container.resolve("optional", {
    allowUnregistered: true
  })
}
```

Source: `packages/core/core-flows/src/api-key/steps/create-api-keys.ts:35-36`

## Service Registration

### Automatic Registration

Medusa automatically registers:

* **Module services** - All module services from `packages/modules/`
* **Core services** - Framework services (logger, event bus, etc.)
* **Custom services** - Services in your `src/services/` directory

### Module Registration

Modules self-register using the `Module` factory:

```typescript theme={null}
import { Module, Modules } from "@medusajs/framework/utils"
import { ApiKeyModuleService } from "@services"

export default Module(Modules.API_KEY, {
  service: ApiKeyModuleService,
})
```

This registers `ApiKeyModuleService` under the key `Modules.API_KEY` (which equals `"apiKey"`).

Source: `packages/modules/api-key/src/index.ts`

### Manual Registration

Register services manually in loaders:

```typescript theme={null}
import { asClass, asFunction, asValue } from "@medusajs/deps/awilix"
import type { MedusaContainer } from "@medusajs/framework/types"
import { MyService } from "./services/my-service"

export default async function myLoader(
  container: MedusaContainer
) {
  // Register as class (new instance per resolution)
  container.register({
    myService: asClass(MyService).singleton(),
  })

  // Register as function factory
  container.register({
    myFactory: asFunction((container) => {
      return container.resolve("dependency")
    }).singleton(),
  })

  // Register as value (constant)
  container.register({
    myConfig: asValue({
      apiKey: process.env.API_KEY,
    }),
  })
}
```

## Registration Modes

Awilix provides three registration modes:

### asClass

Registers a class that will be instantiated:

```typescript theme={null}
import { asClass } from "@medusajs/deps/awilix"

container.register({
  myService: asClass(MyService).singleton(),
})

// Equivalent to: new MyService(dependencies)
```

### asFunction

Registers a factory function:

```typescript theme={null}
import { asFunction } from "@medusajs/deps/awilix"

container.register({
  myFactory: asFunction((cradle) => {
    return new MyService(cradle.dependency)
  }).scoped(),
})
```

### asValue

Registers a constant value:

```typescript theme={null}
import { asValue } from "@medusajs/deps/awilix"

container.register({
  logger: asValue(console),
  config: asValue({ apiUrl: "https://api.example.com" }),
})
```

## Lifetime Management

Control how long instances live:

### Singleton (Default)

One instance for the entire application:

```typescript theme={null}
container.register({
  myService: asClass(MyService).singleton(),
})
```

<Tip>
  Use singleton for stateless services, repositories, and module services. This is the most common lifetime.
</Tip>

### Scoped

New instance per scope (request):

```typescript theme={null}
container.register({
  requestService: asClass(RequestService).scoped(),
})
```

<Note>
  Scoped instances are created once per request scope. Use for services that maintain request-specific state.
</Note>

### Transient

New instance every time:

```typescript theme={null}
container.register({
  temporaryService: asClass(TemporaryService).transient(),
})
```

## Constructor Injection

Services receive dependencies through their constructor:

```typescript theme={null}
import type {
  DAL,
  InternalModuleDeclaration,
  ModulesSdkTypes,
} from "@medusajs/framework/types"
import { MedusaService } from "@medusajs/framework/utils"
import { ApiKey } from "@models"

type InjectedDependencies = {
  baseRepository: DAL.RepositoryService
  apiKeyService: ModulesSdkTypes.IMedusaInternalService<any>
}

export class ApiKeyModuleService
  extends MedusaService<{
    ApiKey: { dto: ApiKeyTypes.ApiKeyDTO }
  }>({ ApiKey })
{
  protected baseRepository_: DAL.RepositoryService
  protected readonly apiKeyService_: ModulesSdkTypes.IMedusaInternalService

  constructor(
    { baseRepository, apiKeyService }: InjectedDependencies,
    protected readonly moduleDeclaration: InternalModuleDeclaration
  ) {
    super(...arguments)
    this.baseRepository_ = baseRepository
    this.apiKeyService_ = apiKeyService
  }
}
```

Source: `packages/modules/api-key/src/services/api-key-module-service.ts:39-63`

<Warning>
  Constructor parameter names must match the registration keys in the container. Use destructuring to make dependencies explicit.
</Warning>

## Resolution Patterns

### In Workflow Steps

Steps receive the container in their context:

```typescript theme={null}
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
import { Modules } from "@medusajs/framework/utils"
import type { IApiKeyModuleService } from "@medusajs/framework/types"

export const createApiKeysStep = createStep(
  "create-api-keys",
  async (data: CreateApiKeysStepInput, { container }) => {
    const service = container.resolve<IApiKeyModuleService>(Modules.API_KEY)
    const created = await service.createApiKeys(data.api_keys)
    return new StepResponse(created)
  }
)
```

### In API Routes

API routes access the container via `req.scope`:

```typescript theme={null}
import type {
  AuthenticatedMedusaRequest,
  MedusaResponse,
} from "@medusajs/framework/http"
import { Modules } from "@medusajs/framework/utils"

export const GET = async (
  req: AuthenticatedMedusaRequest,
  res: MedusaResponse
) => {
  const productModule = req.scope.resolve(Modules.PRODUCT)
  const products = await productModule.listProducts()
  
  res.json({ products })
}
```

### In Subscribers

Event subscribers receive the container:

```typescript theme={null}
import type {
  SubscriberArgs,
  SubscriberConfig,
} from "@medusajs/framework"
import { Modules } from "@medusajs/framework/utils"

export default async function productCreatedHandler({
  event: { data },
  container,
}: SubscriberArgs<{ id: string }>) {
  const productModule = container.resolve(Modules.PRODUCT)
  const product = await productModule.retrieveProduct(data.id)
  
  // Handle product created event
}

export const config: SubscriberConfig = {
  event: "product.created",
}
```

## Container Registration Keys

Medusa provides constants for common services:

```typescript theme={null}
import { ContainerRegistrationKeys, Modules } from "@medusajs/framework/utils"

// Module services
const productModule = container.resolve(Modules.PRODUCT)
const orderModule = container.resolve(Modules.ORDER)

// Core services  
const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
const query = container.resolve(ContainerRegistrationKeys.QUERY)
const remoteQuery = container.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const remoteLink = container.resolve(ContainerRegistrationKeys.REMOTE_LINK)
```

<CodeGroup>
  ```typescript Module Keys theme={null}
  Modules.PRODUCT
  Modules.ORDER
  Modules.CART
  Modules.PAYMENT
  Modules.CUSTOMER
  Modules.API_KEY
  // ... 30+ more
  ```

  ```typescript Core Keys theme={null}
  ContainerRegistrationKeys.LOGGER
  ContainerRegistrationKeys.QUERY
  ContainerRegistrationKeys.REMOTE_QUERY
  ContainerRegistrationKeys.REMOTE_LINK
  ContainerRegistrationKeys.EVENT_BUS
  ```
</CodeGroup>

## Scoped Containers

Create child containers for isolated scopes:

```typescript theme={null}
import type { MedusaContainer } from "@medusajs/framework/types"
import { asValue } from "@medusajs/deps/awilix"

function handleRequest(
  parentContainer: MedusaContainer,
  userId: string
) {
  // Create a scoped container
  const scopedContainer = parentContainer.createScope()
  
  // Register request-specific values
  scopedContainer.register({
    currentUser: asValue({ id: userId }),
  })
  
  // Use scoped container
  const service = scopedContainer.resolve("myService")
  
  // Clean up (if needed)
  scopedContainer.dispose()
}
```

## Testing with DI

DI makes testing easier by allowing mock dependencies:

```typescript theme={null}
import { createContainer, asClass, asValue } from "@medusajs/deps/awilix"
import { MyService } from "../services/my-service"

describe("MyService", () => {
  let container
  let service

  beforeEach(() => {
    container = createContainer()
    
    // Register mocks
    container.register({
      logger: asValue({
        info: jest.fn(),
        error: jest.fn(),
      }),
      productModule: asValue({
        listProducts: jest.fn().mockResolvedValue([]),
      }),
    })
    
    // Register service under test
    container.register({
      myService: asClass(MyService).singleton(),
    })
    
    service = container.resolve("myService")
  })

  it("should do something", async () => {
    await service.doSomething()
    expect(container.resolve("logger").info).toHaveBeenCalled()
  })
})
```

## Best Practices

<CardGroup cols={2}>
  <Card title="Use Constructor Injection">
    Always inject dependencies through the constructor, not properties.
  </Card>

  <Card title="Depend on Interfaces">
    Type dependencies with interfaces, not concrete classes.
  </Card>

  <Card title="Prefer Singleton">
    Use singleton lifetime for stateless services to reduce memory.
  </Card>

  <Card title="Avoid Container in Services">
    Don't inject the container itself - inject specific dependencies.
  </Card>
</CardGroup>

<Warning>
  Avoiding injecting the entire container into services. This creates a service locator anti-pattern and hides dependencies. Always inject specific services.
</Warning>

## Common Patterns

### Service with Multiple Dependencies

```typescript theme={null}
type InjectedDependencies = {
  baseRepository: DAL.RepositoryService
  productService: ModulesSdkTypes.IMedusaInternalService
  productVariantService: ModulesSdkTypes.IMedusaInternalService
  productCategoryService: ProductCategoryService
  [Modules.EVENT_BUS]?: IEventBusModuleService
}

export default class ProductModuleService extends MedusaService {
  protected baseRepository_: DAL.RepositoryService
  protected readonly productService_: ModulesSdkTypes.IMedusaInternalService
  protected readonly productVariantService_: ModulesSdkTypes.IMedusaInternalService
  protected readonly productCategoryService_: ProductCategoryService
  protected readonly eventBusModuleService_?: IEventBusModuleService

  constructor(
    {
      baseRepository,
      productService,
      productVariantService,
      productCategoryService,
      [Modules.EVENT_BUS]: eventBusModuleService,
    }: InjectedDependencies,
    protected readonly moduleDeclaration: InternalModuleDeclaration
  ) {
    super(...arguments)
    this.baseRepository_ = baseRepository
    this.productService_ = productService
    this.productVariantService_ = productVariantService
    this.productCategoryService_ = productCategoryService
    this.eventBusModuleService_ = eventBusModuleService
  }
}
```

Source: `packages/modules/product/src/services/product-module-service.ts:155-190`

### Optional Dependencies

Mark optional dependencies with `?`:

```typescript theme={null}
type InjectedDependencies = {
  logger: Logger
  cache?: ICacheService  // Optional
}

class MyService {
  protected logger_: Logger
  protected cache_?: ICacheService

  constructor({ logger, cache }: InjectedDependencies) {
    this.logger_ = logger
    this.cache_ = cache
  }

  async getData(key: string) {
    // Use cache if available
    if (this.cache_) {
      const cached = await this.cache_.get(key)
      if (cached) return cached
    }
    
    // Fallback to direct fetch
    return await this.fetchData(key)
  }
}
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Services" href="/concepts/services">
    Learn how to build services with DI
  </Card>

  <Card title="Modules" href="/concepts/modules">
    Understand Medusa's module architecture
  </Card>
</CardGroup>
