Back

Dependency injection is a core pattern in Nuxt and Vue for managing shared services across your application. However, the consuming code cannot know if the dependency has been properly provided. So we need to check for its existence.

Here’s a tip I always use to centralize dependency access and error handling to ensure consistency in my Nuxt 3 applications.

Looking for vanilla Vue examples? Jump to the final section!

The problem: Ensuring dependencies are properly provided

Say you have a Nuxt Plugin, that provides a service to your Nuxt app:

plugins/auth.ts

export default defineNuxtPlugin(() => {
     const authService = new AuthService();
     return {
         provide: {
             auth: authService
         }
     }
 });

At runtime, there's no guarantee that the service will be provided. So before using it, you need to test if it has been provided:

composables/useAuth.ts:

export function useAuth() {
  const { $auth } = useNuxtApp();

  if (!$auth) {
    throw new Error('No auth service injected');
  }

  // your composable logic
}

This gets redundant as you need check in every composable using the service. Throwing error codes becomes the responsibility of each composable, leading to potential inconsistency in error messages and debugging.

The solution: a centralized helper composable

The solution is simple, create a centralized helper composable to access the dependency:

composables/useAuth.ts:

import { useNuxtApp } from '#app'

export function useAuth() {
  const { $auth } = useNuxtApp()

  if (!$auth) {
    throw new Error('[Dependency Injection] $auth is not injected. Please ensure the plugin is properly configured.')
  }

  return $auth
}

Now, you can safely use your auth service without additional null checks in your composables. Tada 🎉

Key benefits of using a centralized helper composable:

  • Centralizes error handling, ensuring consistency
  • Eliminates redundant null checks across the application
  • Adheres to DRY principles for cleaner, maintainable code

Using a centralized composable also makes it easier to enforce type safety, making the code more predictable and easier to debug.

Want to dive deeper into how to dependency injection works in Vue? Read my guide on Implementing the Dependency Injection pattern in Vue.


Continue reading to learn how can achieve this in a standalone Vue application.

Vue differences

In a vanilla Vue application, you typically provide dependencies by using a Vue Plugin with app-level provide:

src/plugins/auth-plugin.ts:

import type { App } from 'vue';
import { AuthService } from '@/services/auth.ts';

export default {
  install(app: App) {
    const authService = new AuthService();
    app.provide('auth', authService);
  },
};

In a vanilla Vue application, you can retrieve the provided dependency using inject:

src/composables/useUsers.ts:

export function useUsers() {
  const authService = inject('auth');

  if (!authService) {
    throw new Error('No auth service injected');
  }

  // your composable logic
}

The solution is the same—wrapping the dependency retrieval logic in a dedicated composable!

Last updated on December 24, 2024.

👋 About the author

I'm Laurent, a freelance developer experience engineer that helps dev tools build great onboarding experiences.

I specialize in technical writing, UX writing, and full-stack development.

✍️ Related posts

Read my other articles.

💌 Get in touch

Find me on Bluesky, X (formerly Twitter), LinkedIn, and Github.

You can also contact me via email.