Create SKILL.md
This commit is contained in:
324
skills/api-endpoint-builder/SKILL.md
Normal file
324
skills/api-endpoint-builder/SKILL.md
Normal file
@@ -0,0 +1,324 @@
|
||||
---
|
||||
name: api-endpoint-builder
|
||||
description: "Builds production-ready REST API endpoints with validation, error handling, authentication, and documentation. Follows best practices for security and scalability."
|
||||
category: development
|
||||
risk: safe
|
||||
source: community
|
||||
date_added: "2026-03-05"
|
||||
---
|
||||
|
||||
# API Endpoint Builder
|
||||
|
||||
Build complete, production-ready REST API endpoints with proper validation, error handling, authentication, and documentation.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- User asks to "create an API endpoint" or "build a REST API"
|
||||
- Building new backend features
|
||||
- Adding endpoints to existing APIs
|
||||
- User mentions "API", "endpoint", "route", or "REST"
|
||||
- Creating CRUD operations
|
||||
|
||||
## What You'll Build
|
||||
|
||||
For each endpoint, you create:
|
||||
- Route handler with proper HTTP method
|
||||
- Input validation (request body, params, query)
|
||||
- Authentication/authorization checks
|
||||
- Business logic
|
||||
- Error handling
|
||||
- Response formatting
|
||||
- API documentation
|
||||
- Tests (if requested)
|
||||
|
||||
## Endpoint Structure
|
||||
|
||||
### 1. Route Definition
|
||||
|
||||
```javascript
|
||||
// Express example
|
||||
router.post('/api/users', authenticate, validateUser, createUser);
|
||||
|
||||
// Fastify example
|
||||
fastify.post('/api/users', {
|
||||
preHandler: [authenticate],
|
||||
schema: userSchema
|
||||
}, createUser);
|
||||
```
|
||||
|
||||
### 2. Input Validation
|
||||
|
||||
Always validate before processing:
|
||||
|
||||
```javascript
|
||||
const validateUser = (req, res, next) => {
|
||||
const { email, name, password } = req.body;
|
||||
|
||||
if (!email || !email.includes('@')) {
|
||||
return res.status(400).json({ error: 'Valid email required' });
|
||||
}
|
||||
|
||||
if (!name || name.length < 2) {
|
||||
return res.status(400).json({ error: 'Name must be at least 2 characters' });
|
||||
}
|
||||
|
||||
if (!password || password.length < 8) {
|
||||
return res.status(400).json({ error: 'Password must be at least 8 characters' });
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
```
|
||||
|
||||
### 3. Handler Implementation
|
||||
|
||||
```javascript
|
||||
const createUser = async (req, res) => {
|
||||
try {
|
||||
const { email, name, password } = req.body;
|
||||
|
||||
// Check if user exists
|
||||
const existing = await db.users.findOne({ email });
|
||||
if (existing) {
|
||||
return res.status(409).json({ error: 'User already exists' });
|
||||
}
|
||||
|
||||
// Hash password
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
||||
// Create user
|
||||
const user = await db.users.create({
|
||||
email,
|
||||
name,
|
||||
password: hashedPassword,
|
||||
createdAt: new Date()
|
||||
});
|
||||
|
||||
// Don't return password
|
||||
const { password: _, ...userWithoutPassword } = user;
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: userWithoutPassword
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Create user error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### HTTP Status Codes
|
||||
- `200` - Success (GET, PUT, PATCH)
|
||||
- `201` - Created (POST)
|
||||
- `204` - No Content (DELETE)
|
||||
- `400` - Bad Request (validation failed)
|
||||
- `401` - Unauthorized (not authenticated)
|
||||
- `403` - Forbidden (not authorized)
|
||||
- `404` - Not Found
|
||||
- `409` - Conflict (duplicate)
|
||||
- `500` - Internal Server Error
|
||||
|
||||
### Response Format
|
||||
|
||||
Consistent structure:
|
||||
|
||||
```javascript
|
||||
// Success
|
||||
{
|
||||
"success": true,
|
||||
"data": { ... }
|
||||
}
|
||||
|
||||
// Error
|
||||
{
|
||||
"error": "Error message",
|
||||
"details": { ... } // optional
|
||||
}
|
||||
|
||||
// List with pagination
|
||||
{
|
||||
"success": true,
|
||||
"data": [...],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"limit": 20,
|
||||
"total": 100
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Security Checklist
|
||||
|
||||
- [ ] Authentication required for protected routes
|
||||
- [ ] Authorization checks (user owns resource)
|
||||
- [ ] Input validation on all fields
|
||||
- [ ] SQL injection prevention (use parameterized queries)
|
||||
- [ ] Rate limiting on public endpoints
|
||||
- [ ] No sensitive data in responses (passwords, tokens)
|
||||
- [ ] CORS configured properly
|
||||
- [ ] Request size limits set
|
||||
|
||||
### Error Handling
|
||||
|
||||
```javascript
|
||||
// Centralized error handler
|
||||
app.use((err, req, res, next) => {
|
||||
console.error(err.stack);
|
||||
|
||||
// Don't leak error details in production
|
||||
const message = process.env.NODE_ENV === 'production'
|
||||
? 'Internal server error'
|
||||
: err.message;
|
||||
|
||||
res.status(err.status || 500).json({ error: message });
|
||||
});
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### CRUD Operations
|
||||
|
||||
```javascript
|
||||
// Create
|
||||
POST /api/resources
|
||||
Body: { name, description }
|
||||
|
||||
// Read (list)
|
||||
GET /api/resources?page=1&limit=20
|
||||
|
||||
// Read (single)
|
||||
GET /api/resources/:id
|
||||
|
||||
// Update
|
||||
PUT /api/resources/:id
|
||||
Body: { name, description }
|
||||
|
||||
// Delete
|
||||
DELETE /api/resources/:id
|
||||
```
|
||||
|
||||
### Pagination
|
||||
|
||||
```javascript
|
||||
const getResources = async (req, res) => {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = parseInt(req.query.limit) || 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [resources, total] = await Promise.all([
|
||||
db.resources.find().skip(skip).limit(limit),
|
||||
db.resources.countDocuments()
|
||||
]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: resources,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
pages: Math.ceil(total / limit)
|
||||
}
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
### Filtering & Sorting
|
||||
|
||||
```javascript
|
||||
const getResources = async (req, res) => {
|
||||
const { status, sort = '-createdAt' } = req.query;
|
||||
|
||||
const filter = {};
|
||||
if (status) filter.status = status;
|
||||
|
||||
const resources = await db.resources
|
||||
.find(filter)
|
||||
.sort(sort)
|
||||
.limit(20);
|
||||
|
||||
res.json({ success: true, data: resources });
|
||||
};
|
||||
```
|
||||
|
||||
## Documentation Template
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* @route POST /api/users
|
||||
* @desc Create a new user
|
||||
* @access Public
|
||||
*
|
||||
* @body {string} email - User email (required)
|
||||
* @body {string} name - User name (required)
|
||||
* @body {string} password - Password, min 8 chars (required)
|
||||
*
|
||||
* @returns {201} User created successfully
|
||||
* @returns {400} Validation error
|
||||
* @returns {409} User already exists
|
||||
* @returns {500} Server error
|
||||
*
|
||||
* @example
|
||||
* POST /api/users
|
||||
* {
|
||||
* "email": "user@example.com",
|
||||
* "name": "John Doe",
|
||||
* "password": "securepass123"
|
||||
* }
|
||||
*/
|
||||
```
|
||||
|
||||
## Testing Example
|
||||
|
||||
```javascript
|
||||
describe('POST /api/users', () => {
|
||||
it('should create a new user', async () => {
|
||||
const response = await request(app)
|
||||
.post('/api/users')
|
||||
.send({
|
||||
email: 'test@example.com',
|
||||
name: 'Test User',
|
||||
password: 'password123'
|
||||
});
|
||||
|
||||
expect(response.status).toBe(201);
|
||||
expect(response.body.success).toBe(true);
|
||||
expect(response.body.data.email).toBe('test@example.com');
|
||||
expect(response.body.data.password).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should reject invalid email', async () => {
|
||||
const response = await request(app)
|
||||
.post('/api/users')
|
||||
.send({
|
||||
email: 'invalid',
|
||||
name: 'Test User',
|
||||
password: 'password123'
|
||||
});
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(response.body.error).toContain('email');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Key Principles
|
||||
|
||||
- Validate all inputs before processing
|
||||
- Use proper HTTP status codes
|
||||
- Handle errors gracefully
|
||||
- Never expose sensitive data
|
||||
- Keep responses consistent
|
||||
- Add authentication where needed
|
||||
- Document your endpoints
|
||||
- Write tests for critical paths
|
||||
|
||||
## Related Skills
|
||||
|
||||
- `@security-auditor` - Security review
|
||||
- `@test-driven-development` - Testing
|
||||
- `@database-design` - Data modeling
|
||||
Reference in New Issue
Block a user