メインコンテンツまでスキップ

Cursor を使用した API 構築:REST、GraphQL、FastAPI

Cursor API Development

堅牢な API を構築するには、慎重な計画、一貫したパターン、徹底的なテストが必要です。Cursor の AI 機能は、エンドポイントの足場から OpenAPI ドキュメントの生成まで、API 開発のあらゆる段階を加速できます。このガイドでは、REST、GraphQL、FastAPI のワークフローについて説明します。

前提条件​

開始する前に:

  • Cursor を Pro プランでインストール(推奨)
  • Node.js または Python をインストール
  • HTTP の概念と JSON に精通していること

REST API 開発​

プロジェクトの足場​

Cursor に完全な REST API を足場組みさせます:

以下を使用した Express.js の REST API を作成してください:
- TypeScript
- src/routes/ にルートハンドラ
- ログとエラー処理のミドルウェア
- Zod を使用した入力検証
- JWT 認証
- レート制限
- Swagger ドキュメント

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;

統一されたエラー処理​

// 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',
});
};

Cursor を使用した FastAPI 開発​

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")

# Pydantic モデル
class UserCreate(BaseModel):
email: EmailStr
name: str
password: str

class UserResponse(BaseModel):
id: int
email: str
name: str

class Config:
from_attributes = True

# メモリ内ストレージ(データベースに置き換える)
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)

依存性注入​

# 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

# 使用法
@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()

GraphQL API 開発​

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!
}
`;

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);
},
},
};
GraphQL スキーマ設計

Cursor を使用して、データベーススキーマから GraphQL スキーマを生成します。Prisma スキーマを貼り付け、対応する GraphQL 型定義をリクエストしてください。

認証と認可​

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);
};

ミドルウェアパターン​

// src/middleware/rateLimit.ts
import rateLimit from 'express-rate-limit';

export const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 分
max: 100, // windowMs ごとに IP あたり 100 リクエストまで
message: {
success: false,
error: 'Too many requests, please try again later',
},
standardHeaders: true,
legacyHeaders: false,
});

API ドキュメント​

FastAPI の OpenAPI​

FastAPI は自動的に OpenAPI ドキュメントを生成します。アクセス先:

  • Swagger UI: /docs
  • ReDoc: /redoc

Swagger を使用した Express.js​

// 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);

API のテスト​

統合テストの例​

// 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);
expect(response.body.data.email).toBe('[email protected]');
});

it('should reject invalid email', async () => {
await request(app)
.post('/users')
.send({
email: 'invalid-email',
name: 'Test',
password: 'password',
})
.expect(400);
});
});
});

API 設計のベストプラクティス​

プラクティス実装
バージョニングURL で /api/v1/ プレフィックスを使用
ページネーション{ data, meta: { page, limit, total } } を返す
フィルタリングクエリパラメータを使用:?status=active&sort=created_at
エラーフォーマット一貫した { success: false, error: "message" }
レート制限公開エンドポイントで 15 分あたり 100 リクエスト
キャッシングETag ヘッダーと Redis を使用したレスポンスキャッシュ

一般的な API パターン​

リポジトリパターン​

// 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 } });
}
}

環境設定​

# .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

クイックリファレンス​

タスクCursor プロンプト
CRUD エンドポイントを生成"検証付きの Product リソースの CRUD エンドポイントを作成する"
認証を追加"すべての保護されたルートに JWT 認証ミドルウェアを追加する"
GraphQL スキーマを作成"この Prisma スキーマから GraphQL タイプを生成する"
テストを作成"ユーザー登録エンドポイントの統合テストを作成する"
レート制限を追加"認証と公開で異なる階層のレート制限を API に追加する"

関連リソース​

最終更新:2025 年 7 月