Claude-skill-registry innozverse-api-style
Follow API development conventions including RESTful design, Fastify patterns, Zod validation, error handling, and versioning. Use when building API endpoints, adding routes, or working with API code.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/api-style" ~/.claude/skills/majiayu000-claude-skill-registry-innozverse-api-style && rm -rf "$T"
manifest:
skills/data/api-style/SKILL.mdsafety · automated scan (low risk)
This is a pattern-based risk scan, not a security review. Our crawler flagged:
- references .env files
- references API keys
Always read a skill's source content before installing. Patterns alone don't mean the skill is malicious — but they warrant attention.
source content
innozverse API Development Style
When developing API endpoints for innozverse, follow these patterns and conventions.
Fastify Basics
Route Registration
// apps/api/src/routes/v1/users.ts import { FastifyInstance } from 'fastify'; export async function usersRoutes(fastify: FastifyInstance) { fastify.get('/users', async (request, reply) => { return { users: [] }; }); fastify.get('/users/:id', async (request, reply) => { const { id } = request.params as { id: string }; return { user: { id } }; }); fastify.post('/users', async (request, reply) => { const body = request.body; return reply.code(201).send({ user: body }); }); }
Register in Index
// apps/api/src/index.ts import { usersRoutes } from './routes/v1/users'; fastify.register(usersRoutes, { prefix: '/v1' });
Response Patterns
Success Response
return reply.code(200).send({ status: 'ok', data: { /* ... */ } });
Created Response
return reply.code(201).send({ status: 'created', data: { id: newId } });
Error Response
return reply.code(400).send({ error: 'ValidationError', message: 'Invalid input', statusCode: 400 });
Type Safety
Define Types in @innozverse/shared
// packages/shared/src/types.ts export interface User { id: string; name: string; email: string; } export interface UserResponse { status: 'ok'; data: User; }
Use in API
import { User, UserResponse } from '@innozverse/shared'; fastify.get<{ Reply: UserResponse }>('/users/:id', async (request, reply) => { const user: User = { /* ... */ }; return reply.send({ status: 'ok', data: user }); });
Validation with Zod
Define Schema in @innozverse/shared
// packages/shared/src/schemas.ts import { z } from 'zod'; export const userSchema = z.object({ name: z.string().min(1), email: z.string().email() });
Use in API
import { userSchema } from '@innozverse/shared'; fastify.post('/users', async (request, reply) => { try { const validated = userSchema.parse(request.body); // Use validated data return reply.code(201).send({ data: validated }); } catch (error) { return reply.code(400).send({ error: 'ValidationError', message: error.message }); } });
Error Handling
Global Error Handler
// apps/api/src/index.ts fastify.setErrorHandler((error, request, reply) => { fastify.log.error(error); reply.status(error.statusCode || 500).send({ error: error.name || 'InternalServerError', message: error.message || 'Something went wrong', statusCode: error.statusCode || 500 }); });
Throwing Errors
fastify.get('/users/:id', async (request, reply) => { const user = await findUser(id); if (!user) { return reply.code(404).send({ error: 'NotFound', message: 'User not found', statusCode: 404 }); } return { data: user }; });
Async/Await
Always use async/await, never callbacks:
// ✅ Good fastify.get('/users', async (request, reply) => { const users = await getUsers(); return { users }; }); // ❌ Bad fastify.get('/users', (request, reply) => { getUsers((err, users) => { reply.send({ users }); }); });
RESTful Conventions
Resource Naming
- Plural nouns:
,/users/posts - Nested resources:
/users/:userId/posts
HTTP Methods
- List allGET /resource
- Get oneGET /resource/:id
- CreatePOST /resource
- ReplacePUT /resource/:id
- UpdatePATCH /resource/:id
- DeleteDELETE /resource/:id
Status Codes
- Success (GET, PUT, PATCH, DELETE)200
- Created (POST)201
- No Content (DELETE)204
- Bad Request (validation error)400
- Unauthorized401
- Forbidden403
- Not Found404
- Conflict (duplicate)409
- Internal Server Error500
Versioning
Always version API routes:
// ✅ Good fastify.register(v1Routes, { prefix: '/v1' }); fastify.register(v2Routes, { prefix: '/v2' }); // ❌ Bad fastify.register(routes); // No version
CORS Configuration
import cors from '@fastify/cors'; await fastify.register(cors, { origin: process.env.CORS_ORIGIN || '*', credentials: true });
Environment Variables
const PORT = parseInt(process.env.PORT || '8080', 10); const NODE_ENV = process.env.NODE_ENV || 'development'; // Never hardcode secrets const DB_URL = process.env.DATABASE_URL; // ✅ const API_KEY = process.env.API_KEY; // ✅
Logging
// Use Fastify's built-in logger fastify.log.info('Server starting'); fastify.log.error({ err: error }, 'Error occurred'); fastify.log.debug({ data }, 'Debug info');
Health Check
Always maintain a health check endpoint:
fastify.get('/health', async () => ({ status: 'ok', timestamp: new Date().toISOString(), version: process.env.API_VERSION || '1.0.0' }));
Testing (Future)
// apps/api/src/routes/__tests__/users.test.ts import { buildServer } from '../../index'; describe('Users API', () => { let fastify; beforeAll(async () => { fastify = await buildServer(); }); afterAll(async () => { await fastify.close(); }); test('GET /v1/users returns users list', async () => { const response = await fastify.inject({ method: 'GET', url: '/v1/users' }); expect(response.statusCode).toBe(200); expect(response.json()).toHaveProperty('users'); }); });
Database Patterns (Future)
When adding database:
// Use a connection pool import { Pool } from 'pg'; const pool = new Pool({ connectionString: process.env.DATABASE_URL }); // Close connections gracefully fastify.addHook('onClose', async () => { await pool.end(); }); // Use in routes fastify.get('/users/:id', async (request, reply) => { const { rows } = await pool.query( 'SELECT * FROM users WHERE id = $1', [request.params.id] ); if (rows.length === 0) { return reply.code(404).send({ error: 'Not found' }); } return { data: rows[0] }; });
Best Practices
Single Responsibility
Each route file handles one resource:
routes/v1/ ├── users.ts # User management ├── posts.ts # Post management └── comments.ts # Comment management
DRY Principles
Extract common logic:
// utils/auth.ts export async function requireAuth(request, reply) { const token = request.headers.authorization; if (!token) { return reply.code(401).send({ error: 'Unauthorized' }); } // Verify token } // routes/v1/users.ts fastify.get('/users/me', { preHandler: requireAuth }, async (request, reply) => { return { user: request.user }; });
Graceful Shutdown
process.on('SIGTERM', async () => { await fastify.close(); process.exit(0); });
Anti-Patterns to Avoid
❌ Don't use
any types:
// Bad fastify.get('/users', async (request: any, reply: any) => {
❌ Don't block the event loop:
// Bad fastify.get('/heavy', async () => { let result = 0; for (let i = 0; i < 1000000000; i++) { result += i; } return { result }; });
❌ Don't ignore errors:
// Bad fastify.get('/users', async () => { const users = await getUsers().catch(() => []); return { users }; });
❌ Don't expose internal errors to clients:
// Bad return reply.code(500).send({ error: error.stack }); // Good fastify.log.error(error); return reply.code(500).send({ error: 'Internal Server Error' });