Skill Details
Runtime
(claude-4-6-sonnet)
Example user message
[userMessage]: Resource: Blog posts with title, content, author_id, published (boolean) Framework: Express with MongoDB (Mongoose) Operations: Full CRUD Validation: title required, content required, author_id required
Example agent response
STEP 1 — RESOURCE DESIGN
Resource: post (singular model,
plural route "posts")
Fields:
- title: String, required
- content: String, required
- author_id: ObjectId, required
(reference to User)
- published: Boolean, optional,
default false
- createdAt/updatedAt: auto-managed
by Mongoose timestamps
STEP 2 — ROUTE PLANNING
GET /api/posts — list all posts
Success: 200 with array
Errors: 500 server error
GET /api/posts/:id — get single post
Success: 200 with post object
Errors: 404 not found,
400 invalid ID format,
500 server error
POST /api/posts — create post
Body: {title, content, author_id, published?}
Success: 201 with created post
Errors: 400 validation failure,
500 server error
PUT /api/posts/:id — full update
Body: {title, content, author_id, published}
Success: 200 with updated post
Errors: 400 validation, 404 not found,
500 server error
DELETE /api/posts/:id — delete post
Success: 200 with confirmation message
Errors: 404 not found, 500 server error
STEP 3 — VALIDATION RULES
- title: required, string,
min 1 char, max 200 chars
- content: required, string,
min 1 char
- author_id: required, valid
MongoDB ObjectId format
- published: optional, must be
boolean if provided
STEP 4 — COMPLETE ENDPOINT CODE
```javascript
const express = require('express');
const router = express.Router();
const mongoose = require('mongoose');
const Post = require('../models/Post');
// Validation helper
function validatePostInput(data, isPartial = false) {
const errors = [];
if (!isPartial || data.title !== undefined) {
if (!data.title || typeof data.title !== 'string') {
errors.push('title is required and must be a string');
} else if (data.title.length > 200) {
errors.push('title must be 200 characters or less');
}
}
if (!isPartial || data.content !== undefined) {
if (!data.content || typeof data.content !== 'string') {
errors.push('content is required and must be a string');
}
}
if (!isPartial || data.author_id !== undefined) {
if (!data.author_id || !mongoose.Types.ObjectId.isValid(data.author_id)) {
errors.push('author_id is required and must be a valid ID');
}
}
if (data.published !== undefined && typeof data.published !== 'boolean') {
errors.push('published must be a boolean');
}
return errors;
}
// GET /api/posts - list all posts
router.get('/posts', async (req, res) => {
try {
const posts = await Post.find().sort({ createdAt: -1 });
res.status(200).json({ data: posts });
} catch (error) {
console.error('Error fetching posts:', error);
res.status(500).json({
error: 'Failed to fetch posts'
});
}
});
// GET /api/posts/:id - get single post
router.get('/posts/:id', async (req, res) => {
try {
const { id } = req.params;
if (!mongoose.Types.ObjectId.isValid(id)) {
return res.status(400).json({
error: 'Invalid post ID format'
});
}
const post = await Post.findById(id);
if (!post) {
return res.status(404).json({
error: 'Post not found'
});
}
res.status(200).json({ data: post });
} catch (error) {
console.error('Error fetching post:', error);
res.status(500).json({
error: 'Failed to fetch post'
});
}
});
// POST /api/posts - create post
router.post('/posts', async (req, res) => {
try {
const errors = validatePostInput(req.body);
if (errors.length > 0) {
return res.status(400).json({
error: 'Validation failed',
details: errors
});
}
const post = new Post({
title: req.body.title,
content: req.body.content,
author_id: req.body.author_id,
published: req.body.published || false
});
const savedPost = await post.save();
res.status(201).json({ data: savedPost });
} catch (error) {
console.error('Error creating post:', error);
res.status(500).json({
error: 'Failed to create post'
});
}
});
// PUT /api/posts/:id - full update
router.put('/posts/:id', async (req, res) => {
try {
const { id } = req.params;
if (!mongoose.Types.ObjectId.isValid(id)) {
return res.status(400).json({
error: 'Invalid post ID format'
});
}
const errors = validatePostInput(req.body);
if (errors.length > 0) {
return res.status(400).json({
error: 'Validation failed',
details: errors
});
}
const updatedPost = await Post.findByIdAndUpdate(
id,
{
title: req.body.title,
content: req.body.content,
author_id: req.body.author_id,
published: req.body.published
},
{ new: true, runValidators: true }
);
if (!updatedPost) {
return res.status(404).json({
error: 'Post not found'
});
}
res.status(200).json({ data: updatedPost });
} catch (error) {
console.error('Error updating post:', error);
res.status(500).json({
error: 'Failed to update post'
});
}
});
// DELETE /api/posts/:id - delete post
router.delete('/posts/:id', async (req, res) => {
try {
const { id } = req.params;
if (!mongoose.Types.ObjectId.isValid(id)) {
return res.status(400).json({
error: 'Invalid post ID format'
});
}
const deletedPost = await Post.findByIdAndDelete(id);
if (!deletedPost) {
return res.status(404).json({
error: 'Post not found'
});
}
res.status(200).json({
message: 'Post deleted successfully'
});
} catch (error) {
console.error('Error deleting post:', error);
res.status(500).json({
error: 'Failed to delete post'
});
}
});
module.exports = router;
```
STEP 5 — ERROR RESPONSE STANDARDIZATION
Consistent shape:
```json
{ "error": "message", "details": [] }
```
Success shape:
```json
{ "data": {...} }
```
Status codes used:
- 200: Successful GET/PUT/DELETE
- 201: Successful POST (resource created)
- 400: Validation failure or
malformed ID
- 404: Resource not found
- 500: Unexpected server error
Example error responses:
Validation failure (400):
```json
{
"error": "Validation failed",
"details": [
"title is required and must be a string",
"author_id is required and must be a valid ID"
]
}
```
Not found (404):
```json
{ "error": "Post not found" }
```
STEP 6 — SECURITY AND PRODUCTION NOTES
Not implemented (must be added):
1. Authentication middleware —
these routes have NO auth check.
Anyone can create/edit/delete posts.
Add JWT or session-based auth
middleware before these routes.
2. Authorization — even with auth,
you likely want to verify the
requesting user matches author_id
before allowing PUT/DELETE
(users shouldn't edit others' posts).
3. Rate limiting — consider adding
express-rate-limit to prevent
abuse, especially on POST endpoint.
4. Input sanitization — while type
validation exists, consider adding
HTML sanitization on content field
if it will be rendered as HTML
(XSS prevention).
5. Pagination — GET /posts currently
returns ALL posts with no limit.
Add pagination (limit/skip or
cursor-based) before this scales
beyond a few hundred posts.
Note: This provides complete CRUD
logic and validation. Authentication,
authorization, and infrastructure
setup (database connection,
environment config) require additional
implementation specific to your
application's auth strategy.
By purchasing this skill, you agree to our terms of service
CLAUDE-4-6-SONNET
A structured 6-step REST API endpoint
generation agent for developers using
Cursor.
Analyzes your resource requirements,
then generates complete CRUD endpoints
with proper routing, validation, error
handling, and status codes — following
REST conventions and your framework's
best practices.
Works with Express, FastAPI, and Flask.
Describe your resource and get
production-ready endpoint code instantly.
...more
Added 2 weeks ago
