Use when scaffolding, auditing, or validating MetaSaver data service packages. Covers feature-based structure, service/controller/routes pattern, middleware integration, and API endpoint setup. Requires @metasaver/core-service-utils, {project}-contracts, {project}-database packages. File types: .ts, package.json.
Scaffolds MetaSaver data service packages with Express, Prisma, and feature-based structure. Triggers when creating new services or adding features with service/controller/routes pattern.
/plugin marketplace add metasaver/metasaver-marketplace/plugin install core-claude-plugin@metasaver-marketplaceThis skill is limited to using the following tools:
TEMPLATES.mdtemplates/Dockerfile.templatetemplates/auth.ts.templatetemplates/env.ts.templatetemplates/error.ts.templatetemplates/eslint.config.js.templatetemplates/feature-controller.ts.templatetemplates/feature-index.ts.templatetemplates/feature-service.ts.templatetemplates/features-index.ts.templatetemplates/logging.ts.templatetemplates/main.ts.templatetemplates/package.json.templatetemplates/register.ts.templatetemplates/server.ts.templatetemplates/tsconfig.json.templateThis skill documents the complete file and folder organization for MetaSaver data service packages (e.g., rugby-crm-service). Data services are Express-based API packages that coordinate between database clients and HTTP handlers using a feature-based structure.
Data services provide:
Use when:
packages/services/{project}-service/
├── package.json # Package metadata and scripts
├── tsconfig.json # TypeScript configuration
├── README.md # Package documentation
├── .env.example # Environment variable template
├── eslint.config.js # ESLint configuration (flat config)
├── Dockerfile # Docker build configuration
└── dist/ # Build output (generated)
src/
├── index.ts # Service entry point
├── env.ts # Environment configuration
├── server.ts # Express app factory
├── routes/
│ └── register.ts # Route registration
├── middleware/
│ ├── auth.ts # JWT auth middleware
│ ├── error.ts # Error handling middleware
│ └── logging.ts # Request logging middleware
└── features/
└── {feature}/
├── {feature}.service.ts # Service class (Prisma operations)
└── {feature}.controller.ts # Controller (validation + response)
Complete Example:
packages/services/rugby-crm-service/
├── package.json
├── tsconfig.json
├── .env.example
├── eslint.config.js
├── README.md
├── Dockerfile
├── src/
│ ├── index.ts
│ ├── env.ts
│ ├── server.ts
│ ├── routes/
│ │ └── register.ts
│ ├── middleware/
│ │ ├── auth.ts
│ │ ├── error.ts
│ │ └── logging.ts
│ └── features/
│ ├── users/
│ │ ├── users.service.ts
│ │ └── users.controller.ts
│ └── teams/
│ ├── teams.service.ts
│ └── teams.controller.ts
└── dist/ # Build output (generated)
| Package | Purpose | Key Exports |
|---|---|---|
@metasaver/core-service-utils | Service factory and utilities | createService, ApiError, asyncHandler |
{project}-contracts | Zod validation schemas and types | Validation schemas, types |
{project}-database | Prisma client and database types | prisma, Prisma types |
express | HTTP framework | Express Router, middleware |
jsonwebtoken | JWT token creation and verification | sign, verify |
Required Fields:
name: @metasaver/{project}-service (always scoped)version: 0.1.0 (start with patch version)type: "module" (ESM packages)main: "./dist/index.js"types: "./dist/index.d.ts"Required Scripts:
build: TypeScript compilation (tsc -b)clean: Remove build artifactsdev: Run with tsx for development (tsx watch src/index.ts)start: Production server startuplint, lint:fix, lint:tsc, prettier, prettier:fixtest:unit: Unit tests (stub or actual)See templates/package.json.template for complete template.
Rule: Export a single env object with all configuration, type-safe with defaults.
Variable Naming: {PROJECT_UPPER}_{SETTING_NAME} (e.g., RUGBY_CRM_SERVICE_PORT)
See templates/env.ts.template for implementation.
Rule: Extend base config with proper ESM settings.
See templates/tsconfig.json.template for configuration.
Key Points:
include for eager-loading relationsgetAll, getById, create, update, deleteImport pattern:
// Import order example
import { prisma } from "@metasaver/rugby-crm-database/client";
import type {
User,
CreateUserInput,
UpdateUserInput,
} from "@metasaver/rugby-crm-contracts/users/types";
export class UsersService {
// ... methods
}
See templates/feature-service.ts.template for implementation.
Key Points:
asyncHandler wraps async functions to catch errors automaticallyApiError.notFound() for consistent error responses{ data: {...} } for consistent response formatImport pattern:
// Import order example
import { Router } from "express";
import { asyncHandler, ApiError } from "@metasaver/core-service-utils";
import {
CreateUserRequest,
UpdateUserRequest,
} from "@metasaver/rugby-crm-contracts/users/validation";
import { UsersService } from "#/features/users/users.service.js";
export const router = Router();
const service = new UsersService();
// ... route definitions
See templates/feature-controller.ts.template for implementation.
Key Points:
/api/v1 with versioningImport pattern:
// Import order example
import type { Express } from "express";
import { authMiddleware } from "#/middleware/auth.js";
import { router as usersRouter } from "#/features/users/users.controller.js";
import { router as teamsRouter } from "#/features/teams/teams.controller.js";
export function registerRoutes(app: Express) {
// Health check (no auth)
app.get("/health", (req, res) => res.json({ status: "ok" }));
// API routes (with auth)
app.use("/api/v1/users", authMiddleware, usersRouter);
app.use("/api/v1/teams", authMiddleware, teamsRouter);
}
See templates/register.ts.template for implementation.
Key Points:
See templates/server.ts.template and templates/index.ts.template.
Rule: Always use direct imports from specific files with #/ alias for internal imports.
// CORRECT - Direct imports with #/ alias
import { UsersService } from "#/features/users/users.service.js";
import { router as usersRouter } from "#/features/users/users.controller.js";
// INCORRECT - Do not use barrel exports (index.ts)
import { UsersService, UsersRoutes } from "#/features/users/index.js";
Key principles:
#/ alias for internal imports within service packagefrom "@metasaver/{pkg}/{path}"Create Package Directory
mkdir -p packages/services/{project}-service/src/{features,middleware,routes}
Create Configuration Files (use templates)
package.json, tsconfig.json, .env.example, eslint.config.js, DockerfileCreate Core Service Files (use templates)
src/env.ts, src/server.ts, src/index.tsCreate Middleware Files (use templates)
src/middleware/auth.ts, src/middleware/error.ts, src/middleware/logging.tsCreate Route Registration (use template)
src/routes/register.tsCreate Features (repeat per feature)
src/features/{feature}/{feature}.service.tssrc/features/{feature}/{feature}.controller.tsTest Build and Run
pnpm --filter @metasaver/{project}-service build
pnpm --filter @metasaver/{project}-service dev
mkdir -p src/features/{feature}{feature}.service.ts{feature}.controller.tssrc/routes/register.ts with direct importsImport example in register.ts:
import { router as newFeatureRouter } from "#/features/{feature}/{feature}.controller.js";
app.use("/api/v1/{feature}", authMiddleware, newFeatureRouter);
packages/services/{project}-service/src/features, src/middleware, src/routesdist/ (git-ignored)@metasaver/{project}-service (scoped)0.1.0 (semantic versioning)"module" (ESM)tsconfig.json extends @metasaver/core-typescript-config/basemodule: "ESNext")rootDir: "./src" and outDir: "./dist"src/env.ts exports single env object.env.example exists (committed)src/server.ts exports createServer() functionsrc/index.ts creates server and listens on env.PORTsrc/middleware/auth.ts exists (JWT verification)src/middleware/error.ts exists (error response formatting)src/middleware/logging.ts exists (request logging)src/routes/register.ts centralizes all route mounting/health check available without authentication/api/v1 versioning prefixindex.ts barrel files in featuresasyncHandler#/ alias for internal pathsfrom "@metasaver/{pkg}/{path}"asyncHandler{ data: {...} }pnpm build succeeds without errorspnpm lint:tscViolation: Express handlers not wrapped with asyncHandler
// INCORRECT
router.get("/:id", async (req, res) => { ... });
// CORRECT
router.get("/:id", asyncHandler(async (req, res) => { ... }));
Violation: Service class with HTTP concerns
// INCORRECT - service knows about HTTP
async getUser(req: Request): Promise<Response> { ... }
// CORRECT - service is pure data layer
async getById(id: string): Promise<User | null> { ... }
Violation: Custom validation instead of Zod
// INCORRECT
if (!req.body.name || typeof req.body.name !== "string") { ... }
// CORRECT
const validated = CreateUserSchema.parse(req.body);
Violation: Direct environment variable access throughout code
// INCORRECT - scattered env access
const port = process.env.PORT;
// CORRECT - centralized env object
import { env } from "./env.js";