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-styleInstalls 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-styleDetails
- Type
- skill
- Author
- lastcow
- Slug
- lastcow/api-style
- Created
- 5d ago