skillby lastcow

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.

Installs: 0
Used in: 1 repos
Updated: 1d ago
$npx ai-builder add skill lastcow/api-style

Installs to .claude/skills/api-style/

# innozverse API Development Style

When developing API endpoints for innozverse, follow these patterns and conventions.

## Fastify Basics

### Route Registration
```typescript
// 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
```typescript
// apps/api/src/index.ts
import { usersRoutes } from './routes/v1/users';

fastify.register(usersRoutes, { prefix: '/v1' });
```

## Response Patterns

### Success Response
```typescript
return reply.code(200).send({
  status: 'ok',
  data: { /* ... */ }
});
```

### Created Response
```typescript
return reply.code(201).send({
  status: 'created',
  data: { id: newId }
});
```

### Error Response
```typescript
return reply.code(400).send({
  error: 'ValidationError',
  message: 'Invalid input',
  statusCode: 400
});
```

## Type Safety

### Define Types in @innozverse/shared
```typescript
// packages/shared/src/types.ts
export interface User {
  id: string;
  name: string;
  email: string;
}

export interface UserResponse {
  status: 'ok';
  data: User;
}
```

### Use in API
```typescript
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
```typescript
// 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
```typescript
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
```typescript
// 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
```typescript
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:
```typescript
// ✅ 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:
```typescript
// ✅ Good
fastify.register(v1Routes, { prefix: '/v1' });
fastify.register(v2Routes, { prefix: '/v2' });

// ❌ Bad
fastify.register(routes); // No version
```

## CORS Configuration

```typescript
import cors from '@fastify/cors';

await fastify.register(cors, {
  origin: process.env.CORS_ORIGIN || '*',
  credentials: true
});
```

## Environment Variables

```typescript
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

```typescript
// 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:
```typescript
fastify.get('/health', async () => ({
  status: 'ok',
  timestamp: new Date().toISOString(),
  version: process.env.API_VERSION || '1.0.0'
}));
```

## Testing (Future)

```typescript
// 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:
```typescript
// 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:
```typescript
// 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
```typescript
process.on('SIGTERM', async () => {
  await fastify.close();
  process.exit(0);
});
```

## Anti-Patterns to Avoid

❌ Don't use `any` types:
```typescript
// Bad
fastify.get('/users', async (request: any, reply: any) => {
```

❌ Don't block the event loop:
```typescript
// Bad
fastify.get('/heavy', async () => {
  let result = 0;
  for (let i = 0; i < 1000000000; i++) {
    result += i;
  }
  return { result };
});
```

❌ Don't ignore errors:
```typescript
// Bad
fastify.get('/users', async () => {
  const users = await getUsers().catch(() => []);
  return { users };
});
```

❌ Don't expose internal errors to clients:
```typescript
// Bad
return reply.code(500).send({ error: error.stack });

// Good
fastify.log.error(error);
return reply.code(500).send({ error: 'Internal Server Error' });
```

## References

- [Fastify Documentation](https://www.fastify.io/)
- [Zod Documentation](https://zod.dev/)
- [RESTful API Design](https://restfulapi.net/)

Quick Install

$npx ai-builder add skill lastcow/api-style

Details

Type
skill
Author
lastcow
Slug
lastcow/api-style
Created
5d ago