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.md
safety · 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

  • GET /resource
    - List all
  • GET /resource/:id
    - Get one
  • POST /resource
    - Create
  • PUT /resource/:id
    - Replace
  • PATCH /resource/:id
    - Update
  • DELETE /resource/:id
    - Delete

Status Codes

  • 200
    - Success (GET, PUT, PATCH, DELETE)
  • 201
    - Created (POST)
  • 204
    - No Content (DELETE)
  • 400
    - Bad Request (validation error)
  • 401
    - Unauthorized
  • 403
    - Forbidden
  • 404
    - Not Found
  • 409
    - Conflict (duplicate)
  • 500
    - Internal Server Error

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' });

References