@ferjssilva/fast-crud-api
Version:
A complete and fast crud API generator
728 lines (588 loc) • 19.6 kB
Markdown
# CLAUDE.md - AI Assistant Guide for Fast CRUD API
## Project Overview
**Fast CRUD API** is a lightweight Fastify plugin that automatically generates RESTful CRUD APIs for MongoDB/Mongoose models. This is an NPM package published as `@ferjssilva/fast-crud-api`.
### Key Characteristics
- **Type**: Fastify plugin (using fastify-plugin)
- **Purpose**: Automatic CRUD API generation
- **Database**: MongoDB via Mongoose
- **Version**: 0.1.2
- **Test Coverage**: 100%
- **License**: ISC
## How to Use
### Installation
```bash
npm install @ferjssilva/fast-crud-api
```
**Peer dependencies** (must be installed separately):
```bash
npm install fastify mongoose
```
### Basic Setup
```javascript
const fastify = require('fastify')();
const mongoose = require('mongoose');
const fastCrudApi = require('@ferjssilva/fast-crud-api');
// Connect to MongoDB
await mongoose.connect('mongodb://localhost:27017/mydb');
// Define your Mongoose models
const User = mongoose.model('User', new mongoose.Schema({
name: String,
email: { type: String, unique: true },
age: Number
}));
const Post = mongoose.model('Post', new mongoose.Schema({
title: String,
content: String,
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }
}));
// Register the plugin
await fastify.register(fastCrudApi, {
prefix: '/api',
models: [User, Post],
methods: {
users: ['GET', 'POST', 'PUT', 'DELETE'],
posts: ['GET', 'POST', 'DELETE']
}
});
// Start the server
await fastify.listen({ port: 3000 });
```
### Configuration Options
The plugin accepts the following options:
| Option | Type | Required | Default | Description |
|--------|------|----------|---------|-------------|
| `models` | Array | Yes | - | Array of Mongoose models to generate routes for |
| `prefix` | String | No | `/api` | Base path prefix for all routes |
| `methods` | Object | No | `{}` | HTTP methods allowed per model (by collection name) |
**Methods Configuration**:
- Key: Model collection name (plural, lowercase)
- Value: Array of allowed HTTP methods: `['GET', 'POST', 'PUT', 'DELETE']`
- If a model is not listed or has empty array, no routes are created for it
- Model names are matched case-insensitively
### Generated API Endpoints
For each model with allowed methods, the following routes are generated:
#### Basic CRUD Routes
| Method | Endpoint | Description | Required Permission |
|--------|----------|-------------|---------------------|
| GET | `/api/:model` | List resources with pagination | GET |
| GET | `/api/:model/:id` | Get single resource by ID | GET |
| POST | `/api/:model` | Create new resource | POST |
| PUT | `/api/:model/:id` | Update resource by ID | PUT |
| DELETE | `/api/:model/:id` | Delete resource by ID | DELETE |
#### Nested Routes (for references)
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/:parentModel/:parentId/:childModel` | Get child resources by parent ID |
Example: `GET /api/users/507f1f77bcf86cd799439011/posts` returns all posts for that user.
### API Usage Examples
#### List Resources with Pagination
```http
GET /api/users?page=1&limit=10
Response:
{
"data": [
{ "id": "507f...", "name": "John", "email": "john@example.com", "age": 30 }
],
"pagination": {
"total": 100,
"page": 1,
"limit": 10,
"pages": 10
}
}
```
#### Filter Resources
```http
GET /api/users?age=30&name=John
```
Any query parameter not in the reserved list (page, limit, sort, search, populate) is treated as a filter.
#### Sort Resources
```http
GET /api/users?sort={"createdAt":-1}
GET /api/users?sort={"age":1,"name":-1}
```
Sort parameter accepts JSON string with field names and direction (1 for ascending, -1 for descending).
#### Search Resources
```http
GET /api/users?search=john
```
Searches across all String-type fields using case-insensitive regex matching.
#### Populate References
```http
GET /api/posts?populate=userId
GET /api/posts/507f1f77bcf86cd799439011?populate=userId
```
Populate parameter accepts a field name or array of field names to populate referenced documents.
#### Get Single Resource
```http
GET /api/users/507f1f77bcf86cd799439011
Response:
{
"id": "507f1f77bcf86cd799439011",
"name": "John Doe",
"email": "john@example.com",
"age": 30
}
```
#### Create Resource
```http
POST /api/users
Content-Type: application/json
{
"name": "Jane Doe",
"email": "jane@example.com",
"age": 25
}
Response:
{
"id": "507f1f77bcf86cd799439012",
"name": "Jane Doe",
"email": "jane@example.com",
"age": 25
}
```
#### Update Resource
```http
PUT /api/users/507f1f77bcf86cd799439011
Content-Type: application/json
{
"name": "John Updated",
"age": 31
}
Response:
{
"id": "507f1f77bcf86cd799439011",
"name": "John Updated",
"email": "john@example.com",
"age": 31
}
```
#### Delete Resource
```http
DELETE /api/users/507f1f77bcf86cd799439011
Response:
{
"success": true
}
```
#### Nested Routes
```http
GET /api/users/507f1f77bcf86cd799439011/posts?page=1&limit=5
Response:
{
"data": [
{ "id": "507f...", "title": "My Post", "content": "...", "userId": "507f..." }
],
"pagination": {
"total": 15,
"page": 1,
"limit": 5,
"pages": 3
}
}
```
### Error Responses
All errors follow a consistent format:
```javascript
{
"error": "ErrorType",
"message": "Human-readable error message",
"details": [] // Optional, present for validation errors
}
```
**Common Error Types**:
| Status | Error Type | Description |
|--------|------------|-------------|
| 400 | ValidationError | Mongoose validation failed |
| 400 | InvalidId | Invalid MongoDB ObjectId format |
| 404 | NotFound | Resource not found |
| 409 | DuplicateError | Unique constraint violation |
| 500 | InternalError | Server error |
**Validation Error Example**:
```json
{
"error": "ValidationError",
"message": "Invalid data provided",
"details": [
{
"field": "email",
"message": "Email is required"
}
]
}
```
### Common Use Cases
#### Read-Only API
```javascript
fastify.register(fastCrudApi, {
models: [User, Post],
methods: {
users: ['GET'],
posts: ['GET']
}
});
```
#### Public Read, Authenticated Write
```javascript
// Public routes
fastify.register(fastCrudApi, {
prefix: '/api/public',
models: [Post],
methods: {
posts: ['GET']
}
});
// Admin routes (add authentication middleware separately)
fastify.register(fastCrudApi, {
prefix: '/api/admin',
models: [Post, User],
methods: {
posts: ['GET', 'POST', 'PUT', 'DELETE'],
users: ['GET', 'POST', 'PUT', 'DELETE']
}
});
```
#### Selective Method Access
```javascript
fastify.register(fastCrudApi, {
models: [User, Post, Comment],
methods: {
users: ['GET', 'POST'], // Can read and create users
posts: ['GET', 'POST', 'PUT'], // Can read, create, and update posts
comments: ['GET', 'DELETE'] // Can read and delete comments
}
});
```
## Codebase Structure
```
fast-crud-api/
├── index.js # Entry point - re-exports src/index.js
├── src/
│ ├── index.js # Main plugin module, orchestrates everything
│ ├── routes/
│ │ ├── crud.js # Basic CRUD route handlers (GET, POST, PUT, DELETE)
│ │ └── nested.js # Nested/relationship route handlers
│ ├── middleware/
│ │ └── error-handler.js # Global error handling for MongoDB/validation errors
│ ├── validators/
│ │ └── method.js # HTTP method permission validation
│ └── utils/
│ ├── document.js # MongoDB document transformation utilities
│ └── query.js # Query building with filters, pagination, search
├── tests/ # Mirror structure of src/ for tests
│ ├── routes/
│ ├── middleware/
│ ├── validators/
│ └── utils/
├── package.json
├── jest.config.js
└── README.md
```
## Architecture & Design Patterns
### Plugin Architecture
The project uses **Fastify plugin pattern** with `fastify-plugin` wrapper to ensure proper encapsulation and dependency injection.
**Main Flow** (src/index.js):
1. Error handler setup (global)
2. Loop through provided Mongoose models
3. For each model:
- Setup basic CRUD routes
- Extract reference fields from schema
- Setup nested routes for relationships
### Module Responsibilities
#### 1. **src/index.js** - Main Orchestrator
- Entry point for the Fastify plugin
- Accepts configuration: `models`, `prefix`, `methods`
- Coordinates error handler, CRUD routes, and nested routes setup
- **Key function**: `createRoutes(fastify, options)`
#### 2. **src/routes/crud.js** - CRUD Operations
- **Responsibility**: Generate 5 standard REST endpoints per model
- **Routes Generated**:
- `GET /api/:model` - List with pagination, filtering, search
- `GET /api/:model/:id` - Get single resource
- `POST /api/:model` - Create resource
- `PUT /api/:model/:id` - Update resource
- `DELETE /api/:model/:id` - Delete resource
- **Smart Features**:
- Automatically detects searchable fields (String type fields)
- Automatically detects reference fields for population
- Applies method restrictions via `isMethodAllowed`
- **Key function**: `setupCrudRoutes(fastify, model, baseRoute, options)`
#### 3. **src/routes/nested.js** - Relationship Routes
- **Responsibility**: Generate nested routes for model relationships
- **Example**: If Post has `userId` reference, generates `GET /api/users/:userId/posts`
- **Behavior**: Only creates routes if parent model has GET method enabled
- **Key function**: `setupNestedRoutes(fastify, model, prefix, referenceFields, options)`
#### 4. **src/middleware/error-handler.js** - Error Handling
- **Responsibility**: Convert MongoDB/Mongoose errors to clean JSON responses
- **Handles**:
- `ValidationError` (400) - Mongoose validation failures
- `CastError` (400) - Invalid ObjectId format
- `11000` duplicate key (409) - Unique constraint violations
- Generic errors (500)
- **Key function**: `setupErrorHandler(fastify)`
#### 5. **src/validators/method.js** - Permission Validation
- **Responsibility**: Check if HTTP method is allowed for a specific model
- **Logic**: Case-insensitive model name matching against `methods` config
- **Returns**: Boolean indicating if method is permitted
- **Key function**: `isMethodAllowed(modelName, method, allowedMethods)`
#### 6. **src/utils/query.js** - Query Builder
- **Responsibility**: Build Mongoose queries with advanced features
- **Features**:
- Pagination (page, limit, skip)
- Sorting (default: `{ _id: -1 }`)
- Text search with regex across multiple fields
- Population of referenced documents
- **Key function**: `buildQuery(model, filters, options)`
#### 7. **src/utils/document.js** - Document Transformation
- **Responsibility**: Convert MongoDB documents to clean API responses
- **Transformations**:
- Rename `_id` to `id`
- Remove `__v` (version key)
- Convert ObjectId values to strings
- **Key function**: `transformDocument(doc)`
## Development Workflows
### Testing
```bash
# Run all tests
npm test
# Run with coverage report
npm run test:coverage
# Watch mode for development
npm run test:watch
```
**Testing Philosophy**:
- 100% code coverage requirement
- Tests mirror src/ structure in tests/ directory
- Use Jest with node environment
- Test files named `*.test.js`
### Code Quality
```bash
# Lint source code
npm run lint
```
### Publishing
- Package is scoped: `@ferjssilva/fast-crud-api`
- Public access configured in package.json
- Peer dependencies: fastify >= 4.0.0, mongoose >= 6.0.0
## Code Conventions
### 1. **Naming Conventions**
- **Files**: kebab-case (e.g., `error-handler.js`)
- **Functions**: camelCase (e.g., `setupCrudRoutes`)
- **Constants**: UPPER_SNAKE_CASE (if needed)
- **Model names**: Use `model.collection.name` (plural form)
### 2. **Function Signatures**
All major functions follow this pattern:
```javascript
/**
* Clear JSDoc description
* @param {Type} paramName - Description
* @returns {Type} Description
*/
function functionName(params) {
// Implementation
}
```
### 3. **Error Handling**
- Never throw raw errors
- Always use structured error responses:
```javascript
{
error: 'ErrorType',
message: 'Human-readable message',
details: [] // Optional, for validation errors
}
```
### 4. **Module Exports**
- Use explicit named exports: `module.exports = { functionName }`
- Avoid default exports
### 5. **Route Structure**
- All routes use async/await (no callbacks)
- Routes return data directly (Fastify serializes)
- Use `reply.code()` for non-200 status codes
- Return early pattern for error cases
### 6. **Query Parameters**
Standard query parameters across all list endpoints:
- `page` - Page number (default: 1)
- `limit` - Items per page (default: 10)
- `sort` - JSON string, e.g., `{"createdAt":-1}`
- `search` - Text search term
- `populate` - Field(s) to populate (string or array)
- Any other params treated as filters
## Important Implementation Details
### 1. **Method Validation Logic**
The `isMethodAllowed` function has specific behavior:
- Returns `false` if `allowedMethods` is `null` or `undefined`
- Returns `false` if model not found in `allowedMethods`
- Returns `false` if model's allowed methods array is `null` or empty
- Case-insensitive model name matching
**Recent Update (v0.1.2)**: Fixed validation to properly handle `null` or empty `allowedMethods` arrays.
### 2. **Reference Field Handling**
- Automatically detected from Mongoose schema using `options.ref`
- In filters, reference field values are cast to ObjectId
- Population is opt-in via `populate` query parameter
### 3. **Search Implementation**
- Only searches String type fields
- Uses MongoDB `$regex` with case-insensitive flag
- Combines search with OR conditions across all searchable fields
### 4. **Document Transformation**
Always transform documents before sending to client:
```javascript
return transformDocument(doc); // Single document
return data.map(doc => transformDocument(doc)); // Array
```
### 5. **Pagination Response**
List endpoints always return:
```javascript
{
data: [...],
pagination: {
total: 100,
page: 1,
limit: 10,
pages: 10
}
}
```
## Common Tasks for AI Assistants
### Adding a New Utility Function
1. Create in appropriate `src/utils/*.js` file
2. Add JSDoc documentation
3. Export as named export
4. Create corresponding test in `tests/utils/*.test.js`
5. Ensure 100% coverage
### Adding a New Route Type
1. Consider if it belongs in `crud.js` or `nested.js`
2. Check method permissions using `isMethodAllowed`
3. Use `buildQuery` for list operations
4. Use `transformDocument` for responses
5. Handle errors appropriately
6. Add tests
### Modifying Validation Logic
1. Update `src/validators/method.js`
2. Consider backward compatibility
3. Update all tests in `tests/validators/method.test.js`
4. Update version in package.json if breaking change
### Fixing Bugs
1. Write a failing test first
2. Implement fix
3. Ensure all tests pass
4. Verify 100% coverage maintained
5. Update version appropriately (patch for bugs)
## Testing Guidelines
### Test Structure
```javascript
describe('Component/Module Name', () => {
describe('functionName', () => {
it('should handle specific case', () => {
// Arrange
// Act
// Assert
});
});
});
```
### Coverage Requirements
- **Lines**: 100%
- **Functions**: 100%
- **Branches**: 100%
- **Statements**: 100%
### Mocking
When testing:
- Mock Mongoose models
- Mock Fastify instance
- Mock request/reply objects
- Test both success and error paths
## Version History Context
### Recent Changes (v0.1.2)
- Updated method validation logic
- Now properly handles `null` or empty `allowedMethods` arrays
- Fixed edge cases where methods were incorrectly allowed
### Version Pattern
- **Patch** (0.1.x): Bug fixes, no breaking changes
- **Minor** (0.x.0): New features, backward compatible
- **Major** (x.0.0): Breaking changes
## Dependencies
### Runtime Dependencies
- `fastify-plugin@^5.0.1` - Fastify plugin wrapper
### Peer Dependencies
- `fastify@>=4.0.0` - Web framework
- `mongoose@>=6.0.0` - MongoDB ODM
### Dev Dependencies
- `jest@^29.7.0` - Testing framework
## Best Practices for AI Assistants
### DO:
1. Always read existing tests before modifying code
2. Maintain 100% test coverage
3. Use existing utility functions (don't reinvent)
4. Follow the established patterns in crud.js/nested.js
5. Transform documents before returning to client
6. Handle errors using the error handler
7. Add JSDoc comments to all functions
8. Check method permissions before creating routes
### DON'T:
1. Break the modular structure
2. Add dependencies without careful consideration
3. Skip tests or reduce coverage
4. Mix concerns (keep utils, routes, validators separate)
5. Return raw MongoDB documents
6. Expose internal MongoDB details (_id, __v)
7. Add breaking changes without major version bump
8. Skip error handling
## Debugging Tips
### Common Issues
1. **Routes not created**: Check `isMethodAllowed` returns true
2. **ObjectId casting errors**: Ensure reference fields are properly detected
3. **Population not working**: Verify field has `ref` in schema
4. **Search not working**: Check fields are String type
5. **Tests failing**: Verify mocks match expected function signatures
### Useful Patterns
```javascript
// Check what routes are registered
console.log(fastify.printRoutes());
// Debug query building
const query = buildQuery(model, filters, options);
console.log(query.getQuery());
// Verify schema detection
console.log(model.schema.paths);
```
## File Modification Checklist
When modifying code, verify:
- [ ] Tests updated and passing
- [ ] Coverage still 100%
- [ ] JSDoc comments added/updated
- [ ] No new dependencies added (or justified if needed)
- [ ] Error handling in place
- [ ] Follows existing code style
- [ ] No breaking changes (or version bumped accordingly)
- [ ] README.md updated if user-facing changes
## Configuration Reference
### Plugin Options
```javascript
{
models: [], // Required: Array of Mongoose models
prefix: '/api', // Optional: API route prefix (default: '/api')
methods: { // Optional: Method restrictions
users: ['GET', 'POST'], // Allow only these methods
posts: ['GET'] // Model name matches collection name
}
}
```
### Method Restriction Behavior
- If `methods` not provided: No routes created
- If model not in `methods`: No routes for that model
- If model has empty/null array: No routes for that model
- Methods are: `GET`, `POST`, `PUT`, `DELETE`
## Future Considerations
When extending this library, consider:
- Schema validation (JSON Schema support)
- Custom hooks (pre/post route execution)
- Authentication/authorization integration
- Rate limiting
- Caching strategies
- WebSocket support for real-time updates
- GraphQL alternative
- Soft deletes
- Audit logging
- Field-level permissions
## Summary
This is a well-architected, modular Fastify plugin with excellent test coverage. The code is clean, follows consistent patterns, and is designed for extensibility. When working with this codebase, prioritize maintaining the high quality standards, comprehensive testing, and clear separation of concerns that characterize the current implementation.