vp-implement-module
Standardizes the creation of new NestJS domain modules in the VoxPopuli backend.
Install
mkdir -p .claude/skills/vp-implement-module && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/13276" && unzip -o skill.zip -d .claude/skills/vp-implement-module && rm skill.zipInstalls to .claude/skills/vp-implement-module
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Use when creating a new NestJS module in the VoxPopuli backend - covers service, module, spec file, shared types, and AppModule wiring following project conventionsKey capabilities
- →Create a new NestJS module structure
- →Implement an injectable service with JSDoc and strict TypeScript
- →Configure a NestJS module with imports, providers, and exports
- →Write Jest tests for the service using `Test.createTestingModule()`
- →Wire the new module into `apps/api/src/app/app.module.ts`
- →Add new interfaces to `libs/shared-types/src/lib/shared-types.ts`
How it works
The skill follows a predefined pattern to generate a new NestJS module, including its service, module, and test files, and integrates it into the main application module. It adheres to project-specific conventions for dependency injection and external calls.
Inputs & outputs
When to use vp-implement-module
- →Creating new domain modules
- →Implementing backend services
- →Adding new API features
About this skill
Implement NestJS Module (VoxPopuli)
Overview
Every backend module has the same shape: an injectable service, a module that exports it, a Jest spec, and wiring into AppModule. Third-party APIs and env vars each have their own checklists below.
Module Structure
apps/api/src/{name}/
{name}.service.ts # @Injectable(), constructor DI, JSDoc on public methods
{name}.module.ts # imports deps, provides + exports the service
{name}.service.spec.ts # Jest (not Vitest), Test.createTestingModule()
{name}.controller.ts # only if it exposes HTTP endpoints
- Shared request/response types go in
libs/shared-types/src/lib/shared-types.ts. - Write the service, module, and spec.
- Import the module in
apps/api/src/app/app.module.ts(ConfigModuleis already global).
Calling a Third-Party HTTP API
Use Node's native fetch (no axios or vendor SDK unless it's already in package.json), with AbortSignal.timeout().
- Caching: if the call is an idempotent read of cacheable data (HN items, deterministic lookups), wrap it in
CacheService.getOrSet(key, fetcher, ttl). If it generates fresh or large binary output (TTS audio, LLM generations), call it directly. - Errors: throw a typed error class from the service (e.g.
TtsUpstreamError). The controller maps that class to 502 Bad Gateway with the upstream reason in the message, and everything else to 500. Never pass the upstream status straight to our client: an upstream 401 means our key is wrong, not the user's. - Config: the key and model come from
ConfigService. Fail fast with a clear error if the key is missing. - LLM access: go through
LlmService.getModel(). Never construct LangChain providers directly.
Reference implementation: apps/api/src/tts/tts.service.ts (Mistral Voxtral).
Testing it
Spy on the global fetch and return real Response objects. Never hit the real API from Jest, including "skipped" integration specs.
const fetchMock = jest
.spyOn(global, 'fetch')
.mockResolvedValue(
new Response(JSON.stringify({ translations: [{ text: 'hola' }] }), { status: 200 }),
);
afterEach(() => fetchMock.mockRestore());
it('maps upstream failures to the typed error', async () => {
fetchMock.mockResolvedValueOnce(
new Response(JSON.stringify({ message: 'quota' }), { status: 456 }),
);
await expect(service.translate('hi', 'ES')).rejects.toBeInstanceOf(TranslateUpstreamError);
});
Cover the request shape (URL, auth header, body), success parsing, an HTTP error, a non-JSON error body, and a missing key. Real-API checks belong to vp-e2e-verify, not the test suite.
Adding an Environment Variable
Add it in every one of these places:
| File | What |
|---|---|
apps/api/src/config/env.validation.ts | @IsString() @IsOptional() field (class-validator, not Joi) |
.env.example | Commented placeholder with the default |
render.yaml | sync: false for secrets, value: for plain config |
CLAUDE.md | Environment Variables section |
docs/codebase-summary.md | Env var table |
When you remove or rename a var (or an accepted value, like a provider name), delete it from all of these too and tell the user to clean up the Render dashboard. Until they do, the old value is still deployed; see vp-complete-milestone §2b.
Test Setup Gotcha
Specs that transitively import LlmService or AgentService must mock the providers, because Jest can't load @langchain/* ESM:
jest.mock('../llm/providers/openrouter.provider', () => ({ OpenRouterProvider: jest.fn() }));
jest.mock('../llm/providers/claude.provider', () => ({ ClaudeProvider: jest.fn() }));
jest.mock('../llm/providers/mistral.provider', () => ({ MistralProvider: jest.fn() }));
Common Mistakes
- Importing another module's service directly instead of through DI and module imports
- Forgetting to export the service from its module
- Using
vi.fn()in API specs; the API uses Jest (jest.fn()) jest.mock('axios')orjest.mock('node-fetch'); spy on the globalfetchinstead- Adding an env var to
env.validation.tsbut not torender.yamlor.env.example
Before opening a PR, use vp-complete-milestone.
When not to use it
- →When modifying existing modules
- →When performing frontend work
- →When working on an evaluation harness
Limitations
- →It is not for modifying existing modules.
- →It is not for frontend work.
- →It is not for evaluation harness tasks.
How it compares
This skill automates the creation of a NestJS module following specific project conventions, ensuring consistency and proper integration within the VoxPopuli backend.
Compared to similar skills
vp-implement-module side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| vp-implement-module (this skill) | 0 | 6mo | No flags | Intermediate |
| telegram-mini-app | 62 | 8mo | Review | Advanced |
| stripe-integration | 48 | 4mo | No flags | Advanced |
| nodejs-backend-patterns | 12 | 4mo | No flags | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
You might also like
telegram-mini-app
davila7
Expert in building Telegram Mini Apps (TWA) - web apps that run inside Telegram with native-like experience. Covers the TON ecosystem, Telegram Web App API, payments, user authentication, and building viral mini apps that monetize. Use when: telegram mini app, TWA, telegram web app, TON app, mini app.
stripe-integration
wshobson
Implement Stripe payment processing for robust, PCI-compliant payment flows including checkout, subscriptions, and webhooks. Use when integrating Stripe payments, building subscription systems, or implementing secure checkout flows.
nodejs-backend-patterns
wshobson
Build production-ready Node.js backend services with Express/Fastify, implementing middleware patterns, error handling, authentication, database integration, and API design best practices. Use when creating Node.js servers, REST APIs, GraphQL backends, or microservices architectures.
agent-dev-backend-api
ruvnet
Agent skill for dev-backend-api - invoke with $agent-dev-backend-api
shopify-apps
alinaqi
Shopify app development - Remix, Admin API, checkout extensions
ccxt-typescript
ccxt
CCXT cryptocurrency exchange library for TypeScript and JavaScript developers (Node.js and browser). Covers both REST API (standard) and WebSocket API (real-time). Helps install CCXT, connect to exchanges, fetch market data, place orders, stream live tickers/orderbooks, handle authentication, and manage errors. Use when working with crypto exchanges in TypeScript/JavaScript projects, trading bots, arbitrage systems, or portfolio management tools. Includes both REST and WebSocket examples.