Backend architecture patterns, API design, database optimization, and server-side best practices for Node.js, Express, and Next.js API routes.
name backend-patterns description Backend architecture patterns, API design, database optimization, and server-side best practices for Node.js, Express, and Next.js API routes. バックエンド開発パターン スケーラブルなサーバーサイドアプリケーションのためのバックエンドアーキテクチャパターンとベストプラクティス。 API設計パターン RESTful API構造 // PASS: リソースベースのURL GET /api/markets # リソースのリスト GET /api/markets/:id # 単一リソースの取得 POST /api/markets # リソースの作成 PUT /api/markets/:id # リソースの置換 PATCH /api/markets/:id # リソースの更新 DELETE /api/markets/:id # リソースの削除 // PASS: フィルタリング、ソート、ページネーション用のクエリパラメータ GET /api/markets?status=active&sort=volume&limit= 20 &offset= 0 リポジトリパターン // データアクセスロジックの抽象化 interface MarketRepository { findAll ( filters ?: MarketFilters ): Promise < Market []> findById ( id : string ): Promise < Market | null
create ( data : CreateMarketDto ): Promise < Market
update ( id : string , data : UpdateMarketDto ): Promise < Market
delete ( id : string ): Promise < void
await getUser (market. creator_id ) // Nクエリ } // PASS: 良い: バッチフェッチ const markets = await getMarkets () const creatorIds = markets. map ( m => m. creator_id ) const creators = await getUsers (creatorIds) // 1クエリ const creatorMap = new Map (creators. map ( c => [c. id , c]))
markets. forEach ( market => { market. creator = creatorMap. get (market. creator_id ) }) トランザクションパターン async function createMarketWithPosition ( marketData : CreateMarketDto , positionData : CreatePositionDto ) { // Supabaseトランザクションを使用 const { data, error } = await supabase. rpc ( 'create_market_with_position' , { market_data : marketData, position_data : positionData }) if (error) throw new Error ( 'Transaction failed' ) return data } // SupabaseのSQL関数 CREATE OR REPLACE FUNCTION create_market_with_position ( market_data jsonb, position_data jsonb ) RETURNS jsonb LANGUAGE plpgsql AS $$ BEGIN -- トランザクションは自動的に開始 INSERT INTO markets VALUES (market_data); INSERT INTO positions VALUES (position_data); RETURN jsonb_build_object ( 'success' , true ); EXCEPTION WHEN OTHERS THEN -- ロールバックは自動的に発生 RETURN jsonb_build_object ( 'success' , false , 'error' , SQLERRM ); END ; $$; キャッシング戦略 Redisキャッシングレイヤー class CachedMarketRepository implements MarketRepository { constructor ( private baseRepo : MarketRepository , private redis : RedisClient ) {} async findById ( id : string ): Promise < Market | null
{ // 最初にキャッシュをチェック const cached = await this . redis . get (
market: ${id}) if (cached) { return JSON . parse (cached) } // キャッシュミス - データベースから取得 const market = await this . baseRepo . findById (id) if (market) { // 5分間キャッシュ await this . redis . setex (market: ${id}, 300 , JSON . stringify (market)) } return market } async invalidateCache ( id : string ): Promise < void { await this . redis . del (market: ${id}) } } Cache-Asideパターン async function getMarketWithCache ( id : string ): Promise < Market { const cacheKey =market: ${id}// キャッシュを試す const cached = await redis. get (cacheKey) if (cached) return JSON . parse (cached) // キャッシュミス - DBから取得 const market = await db. markets . findUnique ({ where : { id } }) if (!market) throw new Error ( 'Market not found' ) // キャッシュを更新 await redis. setex (cacheKey, 300 , JSON . stringify (market)) return market } エラーハンドリングパターン 集中エラーハンドラー class ApiError extends Error { constructor ( public statusCode : number , public message : string , public isOperational = true ) { super (message) Object . setPrototypeOf ( this , ApiError . prototype ) } } export function errorHandler ( error : unknown , req : Request ): Response { if (error instanceof ApiError ) { return NextResponse . json ({ success : false , error : error. message }, { status : error. statusCode }) } if (error instanceof z. ZodError ) { return NextResponse . json ({ success : false , error : 'Validation failed' , details : error. errors }, { status : 400 }) } // 予期しないエラーをログに記録 console . error ( 'Unexpected error:' , error) return NextResponse . json ({ success : false , error : 'Internal server error' }, { status : 500 }) } // 使用方法 export async function GET ( request : Request ) { try { const data = await fetchData () return NextResponse . json ({ success : true , data }) } catch (error) { return errorHandler (error, request) } } 指数バックオフによるリトライ async function fetchWithRetry<T>( fn : () => Promise <T>, maxRetries = 3 ): Promise <T> { let lastError : Error for ( let i = 0 ; i < maxRetries; i++) { try { return await fn () } catch (error) { lastError = error as Error if (i < maxRetries - 1 ) { // 指数バックオフ: 1秒、2秒、4秒 const delay = Math . pow ( 2 , i) * 1000 await new Promise ( resolve => setTimeout (resolve, delay)) } } } throw lastError! } // 使用方法 const data = await fetchWithRetry ( () => fetchFromAPI ()) 認証と認可 JWTトークン検証 import jwt from 'jsonwebtoken' interface JWTPayload { userId : string email : string role : 'admin' | 'user' } export function verifyToken ( token : string ): JWTPayload { try { const payload = jwt. verify (token, process. env . JWT_SECRET !) as JWTPayload return payload } catch (error) { throw new ApiError ( 401 , 'Invalid token' ) } } export async function requireAuth ( request : Request ) { const token = request. headers . get ( 'authorization' )?. replace ( 'Bearer ' , '' ) if (!token) { throw new ApiError ( 401 , 'Missing authorization token' ) } return verifyToken (token) } // APIルートでの使用方法 export async function GET ( request : Request ) { const user = await requireAuth (request) const data = await getDataForUser (user. userId ) return NextResponse . json ({ success : true , data }) } ロールベースアクセス制御 type Permission = 'read' | 'write' | 'delete' | 'admin' interface User { id : string role : 'admin' | 'moderator' | 'user' } const rolePermissions : Record < User [ 'role' ], Permission []> = { admin : [ 'read' , 'write' , 'delete' , 'admin' ], moderator : [ 'read' , 'write' , 'delete' ], user : [ 'read' , 'write' ] } export function hasPermission ( user : User , permission : Permission ): boolean { return rolePermissions[user. role ]. includes (permission) } export function requirePermission ( permission : Permission ) { return ( handler : (request: Request, user: User) => Promise < Response
requirePermission ( 'delete' )( async ( request : Request , user : User ) => { // ハンドラーは検証済みの権限を持つ認証済みユーザーを受け取る return new Response ( 'Deleted' , { status : 200 }) } ) レート制限 シンプルなインメモリレートリミッター class RateLimiter { private requests = new Map < string , number []>() async checkLimit ( identifier : string , maxRequests : number , windowMs : number ): Promise < boolean
{ const now = Date . now () const requests = this . requests . get (identifier) || [] // ウィンドウ外の古いリクエストを削除 const recentRequests = requests. filter ( time => now - time < windowMs) if (recentRequests. length = maxRequests) { return false // レート制限超過 } // 現在のリクエストを追加 recentRequests. push (now) this . requests . set (identifier, recentRequests) return true } } const limiter = new RateLimiter () export async function GET ( request : Request ) { const ip = request. headers . get ( 'x-forwarded-for' ) || 'unknown' const allowed = await limiter. checkLimit (ip, 100 , 60000 ) // 100 req/分 if (!allowed) { return NextResponse . json ({ error : 'Rate limit exceeded' }, { status : 429 }) } // リクエストを続行 } バックグラウンドジョブとキュー シンプルなキューパターン class JobQueue <T> { private queue : T[] = [] private processing = false async add ( job : T): Promise < void { this . queue . push (job) if (! this . processing ) { this . process () } } private async process (): Promise < void { this . processing = true while ( this . queue . length
false } private async execute ( job : T): Promise < void
{ // ジョブ実行ロジック } } // マーケットインデックス作成用の使用方法 interface IndexJob { marketId : string } const indexQueue = new JobQueue < IndexJob () export async function POST ( request : Request ) { const { marketId } = await request. json () // ブロッキングの代わりにキューに追加 await indexQueue. add ({ marketId }) return NextResponse . json ({ success : true , message : 'Job queued' }) } ロギングとモニタリング 構造化ロギング interface LogContext { userId ?: string requestId ?: string method ?: string path ?: string [ key : string ]: unknown } class Logger { log ( level : 'info' | 'warn' | 'error' , message : string , context ?: LogContext ) { const entry = { timestamp : new Date (). toISOString (), level, message, ...context } console . log ( JSON . stringify (entry)) } info ( message : string , context ?: LogContext ) { this . log ( 'info' , message, context) } warn ( message : string , context ?: LogContext ) { this . log ( 'warn' , message, context) } error ( message : string , error : Error , context ?: LogContext ) { this . log ( 'error' , message, { ...context, error : error. message , stack : error. stack }) } } const logger = new Logger () // 使用方法 export async function GET ( request : Request ) { const requestId = crypto. randomUUID ()
logger. info ( 'Fetching markets' , { requestId, method : 'GET' , path : '/api/markets' }) try { const markets = await fetchMarkets () return NextResponse . json ({ success : true , data : markets }) } catch (error) { logger. error ( 'Failed to fetch markets' , error as Error , { requestId }) return NextResponse . json ({ error : 'Internal error' }, { status : 500 }) } } 注意 : バックエンドパターンは、スケーラブルで保守可能なサーバーサイドアプリケーションを実現します。複雑さのレベルに適したパターンを選択してください。