UNPKG

@ferjssilva/fast-crud-api

Version:

A complete and fast crud API generator

728 lines (588 loc) 19.6 kB
# 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.