Créer des API avec Cursor : REST, GraphQL et FastAPI

La création d'API robustes nécessite une planification minutieuse, des patterns cohérents et des tests approfondis. Les capacités IA de Cursor peuvent accélérer chaque phase du développement d'API, de l'échafaudage des endpoints à la génération de la documentation OpenAPI. Ce guide couvre les workflows REST, GraphQL et FastAPI.
Prérequis
Avant de commencer :
- Cursor installé avec le plan Pro (recommandé)
- Node.js ou Python installé
- Familiarité avec les concepts HTTP et JSON
Développement d'API REST
Échafaudage du projet
Demandez à Cursor d'échafauder une API REST complète :
Crée une API REST avec Express.js incluant :
- TypeScript
- Gestionnaires de routes dans src/routes/
- Middleware pour la journalisation et la gestion des erreurs
- Validation des entrées avec Zod
- Authentification JWT
- Limitation de débit
- Documentation Swagger
Structure des routes Express.js
// src/routes/users.ts
import { Router } from 'express';
import { z } from 'zod';
import { authenticate } from '../middleware/auth';
import { validate } from '../middleware/validate';
const router = Router();
const createUserSchema = z.object({
email: z.string().email(),
name: z.string().min(2).max(100),
password: z.string().min(8),
});
router.post(
'/',
authenticate,
validate(createUserSchema),
async (req, res, next) => {
try {
const user = await createUser(req.body);
res.status(201).json({
success: true,
data: user,
});
} catch (error) {
next(error);
}
}
);
router.get('/:id', authenticate, async (req, res, next) => {
try {
const user = await getUserById(req.params.id);
if (!user) {
return res.status(404).json({
success: false,
error: 'User not found',
});
}
res.json({ success: true, data: user });
} catch (error) {
next(error);
}
});
export default router;
Gestion des erreurs unifiée
// src/middleware/errorHandler.ts
import { Request, Response, NextFunction } from 'express';
export class AppError extends Error {
constructor(
public statusCode: number,
public message: string,
public isOperational = true
) {
super(message);
Object.setPrototypeOf(this, AppError.prototype);
}
}
export const errorHandler = (
err: Error,
req: Request,
res: Response,
next: NextFunction
) => {
if (err instanceof AppError) {
return res.status(err.statusCode).json({
success: false,
error: err.message,
});
}
console.error('Unexpected error:', err);
res.status(500).json({
success: false,
error: 'Internal server error',
});
};
Développement FastAPI avec Cursor
Création d'endpoints FastAPI
# main.py
from fastapi import FastAPI, HTTPException, Depends, status
from fastapi.security import OAuth2PasswordBearer
from pydantic import BaseModel, EmailStr
from typing import List, Optional
import uvicorn
app = FastAPI(
title="My API",
description="A production-ready API built with FastAPI",
version="1.0.0",
)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# Modèles Pydantic
class UserCreate(BaseModel):
email: EmailStr
name: str
password: str
class UserResponse(BaseModel):
id: int
email: str
name: str
class Config:
from_attributes = True
# Stockage en mémoire (remplacer par une base de données)
users_db = {}
user_counter = 0
@app.post(
"/users",
response_model=UserResponse,
status_code=status.HTTP_201_CREATED,
tags=["users"],
)
async def create_user(user: UserCreate):
global user_counter
user_counter += 1
new_user = {
"id": user_counter,
"email": user.email,
"name": user.name,
}
users_db[user_counter] = new_user
return new_user
@app.get(
"/users/{user_id}",
response_model=UserResponse,
tags=["users"],
)
async def get_user(user_id: int):
if user_id not in users_db:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User not found",
)
return users_db[user_id]
@app.get("/users", response_model=List[UserResponse], tags=["users"])
async def list_users(
skip: int = 0,
limit: int = 100,
):
return list(users_db.values())[skip : skip + limit]
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
Injection de dépendances
# dependencies.py
from fastapi import Depends, HTTPException, status
from sqlalchemy.orm import Session
from database import SessionLocal
async def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
async def get_current_user(token: str = Depends(oauth2_scheme)):
user = verify_token(token)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication",
)
return user
# Utilisation
@app.get("/items")
async def read_items(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
return db.query(Item).filter(Item.owner_id == current_user.id).all()
Développement d'API GraphQL
Configuration Apollo Server
// src/schema.ts
import { gql } from 'apollo-server-express';
export const typeDefs = gql`
type User {
id: ID!
email: String!
name: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
}
type Query {
users: [User!]!
user(id: ID!): User
posts: [Post!]!
post(id: ID!): Post
}
type Mutation {
createUser(email: String!, name: String!, password: String!): User!
createPost(title: String!, content: String!, authorId: ID!): Post!
}
`;
Résolveurs avec Cursor
// src/resolvers.ts
export const resolvers = {
Query: {
users: async (_, __, { dataSources }) => {
return dataSources.userAPI.getAllUsers();
},
user: async (_, { id }, { dataSources }) => {
return dataSources.userAPI.getUserById(id);
},
},
User: {
posts: async (parent, _, { dataSources }) => {
return dataSources.postAPI.getPostsByAuthor(parent.id);
},
},
Mutation: {
createUser: async (_, args, { dataSources }) => {
return dataSources.userAPI.createUser(args);
},
},
};
Conception de schéma GraphQL
Utilisez Cursor pour générer des schémas GraphQL à partir de votre schéma de base de données. Collez votre schéma Prisma et demandez les définitions de types GraphQL correspondantes.
Authentification et autorisation
Implémentation JWT
// src/utils/jwt.ts
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
const JWT_SECRET = process.env.JWT_SECRET!;
const SALT_ROUNDS = 12;
export const hashPassword = async (password: string): Promise<string> => {
return bcrypt.hash(password, SALT_ROUNDS);
};
export const comparePassword = async (
password: string,
hash: string
): Promise<boolean> => {
return bcrypt.compare(password, hash);
};
export const generateToken = (payload: object): string => {
return jwt.sign(payload, JWT_SECRET, { expiresIn: '24h' });
};
export const verifyToken = (token: string): any => {
return jwt.verify(token, JWT_SECRET);
};
Pattern middleware
// src/middleware/rateLimit.ts
import rateLimit from 'express-rate-limit';
export const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limite chaque IP à 100 requêtes par fenêtre
message: {
success: false,
error: 'Too many requests, please try again later',
},
standardHeaders: true,
legacyHeaders: false,
});
Documentation de l'API
OpenAPI avec FastAPI
FastAPI génère automatiquement la documentation OpenAPI. Accédez-y à :
- Swagger UI :
/docs - ReDoc :
/redoc
Express.js avec Swagger
// src/config/swagger.ts
import swaggerJsdoc from 'swagger-jsdoc';
const options = {
definition: {
openapi: '3.0.0',
info: {
title: 'My API',
version: '1.0.0',
description: 'API documentation',
},
servers: [
{
url: 'http://localhost:3000/api',
},
],
},
apis: ['./src/routes/*.ts'],
};
export const specs = swaggerJsdoc(options);
Tester les API
Exemple de test d'intégration
// tests/users.test.ts
import request from 'supertest';
import app from '../src/app';
describe('Users API', () => {
describe('POST /users', () => {
it('should create a new user', async () => {
const response = await request(app)
.post('/users')
.send({
name: 'Test User',
password: 'securePassword123',
})
.expect(201);
expect(response.body.success).toBe(true);
});
it('should reject invalid email', async () => {
await request(app)
.post('/users')
.send({
email: 'invalid-email',
name: 'Test',
password: 'password',
})
.expect(400);
});
});
});
Bonnes pratiques de conception d'API
| Pratique | Implémentation |
|---|---|
| Versionnement | Utiliser le préfixe /api/v1/ dans les URLs |
| Pagination | Retourner { data, meta: { page, limit, total } } |
| Filtrage | Utiliser les params de requête : ?status=active&sort=created_at |
| Format d'erreur | Consistent { success: false, error: "message" } |
| Limitation de débit | 100 requêtes par 15 minutes pour les endpoints publics |
| Mise en cache | Utiliser les en-têtes ETag et Redis pour la mise en cache |
Patterns d'API courants
Pattern Repository
// src/repositories/UserRepository.ts
export class UserRepository {
async findById(id: string): Promise<User | null> {
return prisma.user.findUnique({ where: { id } });
}
async findByEmail(email: string): Promise<User | null> {
return prisma.user.findUnique({ where: { email } });
}
async create(data: CreateUserInput): Promise<User> {
return prisma.user.create({ data });
}
async update(id: string, data: UpdateUserInput): Promise<User> {
return prisma.user.update({ where: { id }, data });
}
async delete(id: string): Promise<void> {
await prisma.user.delete({ where: { id } });
}
}
Configuration de l'environnement
# .env
NODE_ENV=development
PORT=3000
DATABASE_URL=postgresql://user:pass@localhost:5432/myapp
JWT_SECRET=your-secret-key
REDIS_URL=redis://localhost:6379
RATE_LIMIT_MAX=100
Référence rapide
| Tâche | Prompt Cursor |
|---|---|
| Générer des endpoints CRUD | "Crée des endpoints CRUD pour une ressource Product avec validation" |
| Ajouter l'authentification | "Ajoute le middleware d'authentification JWT à toutes les routes protégées" |
| Créer un schéma GraphQL | "Génère les types GraphQL à partir de ce schéma Prisma" |
| Écrire des tests | "Écris des tests d'intégration pour l'endpoint d'inscription utilisateur" |
| Ajouter la limitation de débit | "Ajoute la limitation de débit à l'API avec différents niveaux pour auth vs public" |
Ressources connexes
- Guide de développement de bases de données avec Cursor
- Guide de développement Docker avec Cursor
- Guide de test avec Cursor
Dernière mise à jour : Juillet 2025