Back to Skills

create-utility-service

majiayu000
Updated Today
58
9
58
View on GitHub
Metaai

About

This skill creates utility services for cross-cutting concerns like authentication, email, or external API integrations. Unlike domain services, these don't extend BaseService or inject repositories, focusing instead on shared functionality. Use it when building services that provide specialized capabilities used across multiple parts of your application.

Quick Install

Claude Code

Recommended
Plugin CommandRecommended
/plugin add https://github.com/majiayu000/claude-skill-registry
Git CloneAlternative
git clone https://github.com/majiayu000/claude-skill-registry.git ~/.claude/skills/create-utility-service

Copy and paste this command in Claude Code to install this skill

Documentation

Create Utility Service

Creates a service for cross-cutting concerns or specialized functionality. Unlike resource services, utility services don't extend BaseService and typically don't inject repositories.

Quick Reference

Location: src/services/{service-name}.service.ts Naming: Descriptive, kebab-case (e.g., authentication.service.ts, email.service.ts)

When to Use

Use this skill when creating services that:

  • Call external APIs (auth service, payment gateway, email provider)
  • Provide shared functionality used by other services
  • Handle cross-cutting concerns (authorization, validation, notifications)
  • Don't directly map to a domain entity

Examples: AuthenticationService, AuthorizationService, EmailService, NotificationService

Service Categories

1. External API Services

Services that communicate with external systems.

import { env } from "@/env";
import { ServiceUnavailableError, UnauthenticatedError } from "@/errors";
import { responseSchema, type ResponseType } from "@/schemas/response.schema";

export class ExternalApiService {
  private readonly baseUrl: string;

  constructor() {
    this.baseUrl = env.EXTERNAL_SERVICE_URL;

    if (!this.baseUrl) {
      throw new ServiceUnavailableError(
        "External service is not properly configured.",
      );
    }
  }

  async fetchData(token: string): Promise<ResponseType> {
    try {
      const response = await fetch(`${this.baseUrl}/endpoint`, {
        headers: {
          Authorization: `Bearer ${token}`,
          "Content-Type": "application/json",
        },
      });

      if (!response.ok) {
        this.handleHttpError(response.status);
      }

      const rawData = await response.json();
      return this.validateResponse(rawData);
    } catch (error) {
      this.handleError(error);
    }
  }

  private handleHttpError(status: number): never {
    if (status === 401 || status === 403) {
      throw new UnauthenticatedError("Invalid authentication token");
    }
    throw new ServiceUnavailableError(`External service error: ${status}`);
  }

  private validateResponse(data: unknown): ResponseType {
    const parsed = responseSchema.safeParse(data);
    if (!parsed.success) {
      console.error("Invalid response format:", parsed.error.format());
      throw new ServiceUnavailableError("Invalid response format");
    }
    return parsed.data;
  }

  private handleError(error: unknown): never {
    // Re-throw known domain errors
    if (
      error instanceof UnauthenticatedError ||
      error instanceof ServiceUnavailableError
    ) {
      throw error;
    }

    console.error("External service error:", error);
    throw new ServiceUnavailableError("External service unavailable");
  }
}

Key patterns:

  • Read config from @/env (never process.env directly)
  • Validate responses with Zod schemas
  • Throw domain errors from @/errors
  • Handle and wrap unknown errors

2. Authorization Services

Services that provide permission logic.

import type { AuthenticatedUserContextType } from "@/schemas/user.schemas";
import type { {Entity}Type } from "@/schemas/{entity}.schema";

export class AuthorizationService {
  isAdmin(user: AuthenticatedUserContextType): boolean {
    return user.globalRole === "admin";
  }

  // --- {Entity} Permissions ---

  async canView{Entity}(
    user: AuthenticatedUserContextType,
    {entity}: {Entity}Type,
  ): Promise<boolean> {
    if (this.isAdmin(user)) return true;
    if ({entity}.createdBy === user.userId) return true;
    return false;
  }

  async canCreate{Entity}(user: AuthenticatedUserContextType): Promise<boolean> {
    if (this.isAdmin(user)) return true;
    if (user.globalRole === "user") return true;
    return false;
  }

  async canUpdate{Entity}(
    user: AuthenticatedUserContextType,
    {entity}: {Entity}Type,
  ): Promise<boolean> {
    if (this.isAdmin(user)) return true;
    if ({entity}.createdBy === user.userId) return true;
    return false;
  }

  async canDelete{Entity}(
    user: AuthenticatedUserContextType,
    {entity}: {Entity}Type,
  ): Promise<boolean> {
    if (this.isAdmin(user)) return true;
    if ({entity}.createdBy === user.userId) return true;
    return false;
  }

  // --- Event Permissions ---

  async canReceive{Entity}Event(
    user: AuthenticatedUserContextType,
    {entity}Data: { createdBy: string; [key: string]: unknown },
  ): Promise<boolean> {
    // Apply same rules as viewing
    if (this.isAdmin(user)) return true;
    if ({entity}Data.createdBy === user.userId) return true;
    return false;
  }
}

Key patterns:

  • Methods are async for consistency (even if currently sync)
  • Return boolean not throw errors (let caller decide)
  • Admin check is a shared helper
  • Group permissions by entity with comments

3. Notification/Communication Services

Services that send notifications, emails, or messages.

import { env } from "@/env";
import { ServiceUnavailableError } from "@/errors";

export interface EmailOptions {
  to: string;
  subject: string;
  body: string;
  html?: boolean;
}

export class EmailService {
  private readonly apiKey: string;
  private readonly fromAddress: string;

  constructor() {
    this.apiKey = env.EMAIL_API_KEY;
    this.fromAddress = env.EMAIL_FROM_ADDRESS;

    if (!this.apiKey || !this.fromAddress) {
      throw new ServiceUnavailableError(
        "Email service is not properly configured.",
      );
    }
  }

  async send(options: EmailOptions): Promise<boolean> {
    try {
      // External API call implementation
      return true;
    } catch (error) {
      console.error("Email service error:", error);
      throw new ServiceUnavailableError("Email service unavailable");
    }
  }
}

Patterns & Rules

No BaseService Extension

Utility services are standalone classes - don't extend BaseService:

// Correct
export class AuthenticationService {
  // ...
}

// Wrong - BaseService is for resource services
export class AuthenticationService extends BaseService {
  // ...
}

No Repository Injection

Utility services don't directly access data:

// Correct - calls external API or provides logic
export class AuthenticationService {
  async authenticate(token: string) {
    return fetch(`${this.authUrl}/auth/me`, ...);
  }
}

// Wrong - use resource service for data access
export class AuthenticationService {
  constructor(private userRepository: IUserRepository) {}
}

Multiple Implementations (Provider Pattern)

When you need to support multiple providers (e.g., different email services, notification channels, or payment gateways), create an interface and provide multiple implementations:

// Interface in src/services/email.service.ts
export interface IEmailService {
  send(options: EmailOptions): Promise<EmailResult>;
  sendTemplate(to: string, templateId: string, variables: Record<string, string>): Promise<EmailResult>;
}

// SendGrid implementation in src/services/sendgrid-email.service.ts
export class SendGridEmailService implements IEmailService {
  async send(options: EmailOptions): Promise<EmailResult> {
    // SendGrid-specific implementation
  }
  async sendTemplate(...): Promise<EmailResult> {
    // SendGrid-specific implementation
  }
}

// Mailgun implementation in src/services/mailgun-email.service.ts
export class MailgunEmailService implements IEmailService {
  async send(options: EmailOptions): Promise<EmailResult> {
    // Mailgun-specific implementation
  }
  async sendTemplate(...): Promise<EmailResult> {
    // Mailgun-specific implementation
  }
}

Then inject the interface in dependent services:

export class NotificationService {
  constructor(private emailService: IEmailService) {}

  async notifyUser(userId: string, message: string) {
    await this.emailService.send({
      to: userEmail,
      subject: "Notification",
      body: message,
    });
  }
}

// Usage - choose provider based on config
const emailService =
  env.EMAIL_PROVIDER === "sendgrid"
    ? new SendGridEmailService()
    : new MailgunEmailService();

const notificationService = new NotificationService(emailService);

When to use this pattern:

  • Multiple email providers (SendGrid, Mailgun, SES)
  • Multiple notification channels (email, SMS, push)
  • Multiple payment gateways (Stripe, PayPal)
  • Multiple storage backends (S3, GCS, local)

Error Handling

Use domain errors from @/errors:

import {
  ServiceUnavailableError,
  UnauthenticatedError,
  UnauthorizedError,
} from "@/errors";

// Throw appropriate domain errors
if (!response.ok) {
  throw new ServiceUnavailableError("Service unavailable");
}

// Re-throw known errors, wrap unknown ones
if (error instanceof ServiceUnavailableError) {
  throw error;
}
throw new ServiceUnavailableError("Unknown error");

Configuration

Always read from validated env:

import { env } from "@/env";

// Correct
const apiUrl = env.API_URL;

// Wrong - bypasses validation
const apiUrl = process.env.API_URL;

Response Validation

Always validate external data with Zod:

const rawData = await response.json();
const parsed = schema.safeParse(rawData);

if (!parsed.success) {
  console.error("Invalid format:", parsed.error.format());
  throw new ServiceUnavailableError("Invalid response format");
}

return parsed.data;

Complete Examples

See REFERENCE.md for complete examples:

  • AuthenticationService - External API integration
  • AuthorizationService - Permission logic

What NOT to Do

  • Do NOT extend BaseService (that's for resource services)
  • Do NOT inject repositories (use resource services for data access)
  • Do NOT use process.env directly (use @/env)
  • Do NOT return HTTP status codes (use domain errors)
  • Do NOT swallow errors silently (log and re-throw)

See Also

  • create-service - Guide for choosing service type
  • create-resource-service - CRUD services for domain entities
  • add-env-variable - Adding environment variables for service configuration

GitHub Repository

majiayu000/claude-skill-registry
Path: skills/create-utility-service

Related Skills

sglang

Meta

SGLang is a high-performance LLM serving framework that specializes in fast, structured generation for JSON, regex, and agentic workflows using its RadixAttention prefix caching. It delivers significantly faster inference, especially for tasks with repeated prefixes, making it ideal for complex, structured outputs and multi-turn conversations. Choose SGLang over alternatives like vLLM when you need constrained decoding or are building applications with extensive prefix sharing.

View skill

evaluating-llms-harness

Testing

This Claude Skill runs the lm-evaluation-harness to benchmark LLMs across 60+ standardized academic tasks like MMLU and GSM8K. It's designed for developers to compare model quality, track training progress, or report academic results. The tool supports various backends including HuggingFace and vLLM models.

View skill

langchain

Meta

LangChain is a framework for building LLM applications using agents, chains, and RAG pipelines. It supports multiple LLM providers, offers 500+ integrations, and includes features like tool calling and memory management. Use it for rapid prototyping and deploying production systems like chatbots, autonomous agents, and question-answering services.

View skill

llamaguard

Other

LlamaGuard is Meta's 7-8B parameter model for moderating LLM inputs and outputs across six safety categories like violence and hate speech. It offers 94-95% accuracy and can be deployed using vLLM, Hugging Face, or Amazon SageMaker. Use this skill to easily integrate content filtering and safety guardrails into your AI applications.

View skill