Skip to main content 홈 크리에이터 undermybelt hermes-skills ecc-backend-patterns
ecc-backend-patterns Backend architecture patterns, API design, database optimization, and server-side best practices for Node.js, Express, and Next.js API routes.
설치로 이동 Skills Marketplace 커뮤니티가 만든 AI 스킬을 발견하고 탐색하세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/Undermybelt/hermes-skills --skill ecc-backend-patterns명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
Zip 다운로드 다운로드 중... name ecc-backend-patterns description Backend architecture patterns, API design, database optimization, and server-side best practices for Node.js, Express, and Next.js API routes. origin ECC
Backend Development Patterns
Backend architecture patterns and best practices for scalable server-side applications.
When to Activate
Designing REST or GraphQL API endpoints
Implementing repository, service, or controller layers
Optimizing database queries (N+1, indexing, connection pooling)
Adding caching (Redis, in-memory, HTTP cache headers)
Setting up background jobs or async processing
Structuring error handling and validation for APIs
Building middleware (auth, logging, rate limiting)
API Design Patterns
RESTful API Structure
GET /api/markets # List resources
GET /api/markets/:id # Get single resource
POST /api/markets # Create resource
PUT /api/markets/:id # Replace resource
PATCH /api/markets/:id # Update resource
DELETE /api/markets/:id # resource
/api/markets?status=active&sort=volume&limit= &offset=
Delete
GET
20
0
Repository Pattern
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 >
}
class SupabaseMarketRepository implements MarketRepository {
async findAll (filters ?: MarketFilters ): Promise <Market []> {
let query = supabase.from ('markets' ).select ('*' )
if (filters?.status ) {
query = query.eq ('status' , filters.status )
}
if (filters?.limit ) {
query = query.limit (filters.limit )
}
const { data, error } = await query
if (error) throw new Error (error.message )
return data
}
}
Service Layer Pattern
class MarketService {
constructor (private marketRepo : MarketRepository ) {}
async searchMarkets (query : string , limit : number = 10 ): Promise <Market []> {
const embedding = await generateEmbedding (query)
const results = await this .vectorSearch (embedding, limit)
const markets = await this .marketRepo .findByIds (results.map (r => r.id ))
return markets.sort ((a, b ) => {
const scoreA = results.find (r => r.id === a.id )?.score || 0
const scoreB = results.find (r => r.id === b.id )?.score || 0
return scoreA - scoreB
})
}
private async vectorSearch (embedding : number [], limit : number ) {
}
}
Middleware Pattern
export function withAuth (handler : NextApiHandler ): NextApiHandler {
return async (req, res) => {
const token = req.headers .authorization ?.replace ('Bearer ' , '' )
if (!token) {
return res.status (401 ).json ({ error : 'Unauthorized' })
}
try {
const user = await verifyToken (token)
req.user = user
return handler (req, res)
} catch (error) {
return res.status (401 ).json ({ error : 'Invalid token' })
}
}
}
export default withAuth (async (req, res) => {
})
Database Patterns
Query Optimization
const { data } = await supabase
.from ('markets' )
.select ('id, name, status, volume' )
.eq ('status' , 'active' )
.order ('volume' , { ascending : false })
.limit (10 )
const { data } = await supabase
.from ('markets' )
.select ('*' )
N+1 Query Prevention
const markets = await getMarkets ()
for (const market of markets) {
market.creator = await getUser (market.creator_id )
}
const markets = await getMarkets ()
const creatorIds = markets.map (m => m.creator_id )
const creators = await getUsers (creatorIds)
const creatorMap = new Map (creators.map (c => [c.id , c]))
markets.forEach (market => {
market.creator = creatorMap.get (market.creator_id )
})
Transaction Pattern async function createMarketWithPosition (
marketData : CreateMarketDto ,
positionData : CreatePositionDto
) {
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
}
CREATE OR REPLACE FUNCTION create_market_with_position (
market_data jsonb,
position_data jsonb
)
RETURNS jsonb
LANGUAGE plpgsql
AS $$
BEGIN
-- Start transaction automatically
INSERT INTO markets VALUES (market_data);
INSERT INTO positions VALUES (position_data);
RETURN jsonb_build_object ('success' , true );
EXCEPTION
WHEN OTHERS THEN
-- Rollback happens automatically
RETURN jsonb_build_object ('success' , false , 'error' , SQLERRM );
END ;
$$;
Caching Strategies
Redis Caching Layer 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) {
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 Pattern async function getMarketWithCache (id : string ): Promise <Market > {
const cacheKey = `market:${id} `
const cached = await redis.get (cacheKey)
if (cached) return JSON .parse (cached)
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
}
Error Handling Patterns
Centralized Error Handler 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)
}
}
Retry with Exponential Backoff 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 ) {
const delay = Math .pow (2 , i) * 1000
await new Promise (resolve => setTimeout (resolve, delay))
}
}
}
throw lastError!
}
const data = await fetchWithRetry (() => fetchFromAPI ())
Authentication & Authorization
JWT Token Validation 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)
}
export async function GET (request : Request ) {
const user = await requireAuth (request)
const data = await getDataForUser (user.userId )
return NextResponse .json ({ success : true , data })
}
Role-Based Access Control 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 > ) => {
return async (request : Request ) => {
const user = await requireAuth (request)
if (!hasPermission (user, permission)) {
throw new ApiError (403 , 'Insufficient permissions' )
}
return handler (request, user)
}
}
}
export const DELETE = requirePermission ('delete' )(
async (request : Request , user : User ) => {
return new Response ('Deleted' , { status : 200 })
}
)
Rate Limiting Rate limiting must use a shared store such as Redis, a gateway, or the
platform's native limiter. Do not use per-process in-memory counters for
production APIs: they reset on deploy, split across replicas, and fail open in
serverless or multi-instance environments.
Keep the backend layer responsible for choosing the integration point and error
shape; use api-design for the HTTP contract and security-review for abuse
case review.
Background Jobs & Queues
Simple Queue Pattern 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 > 0 ) {
const job = this .queue .shift ()!
try {
await this .execute (job)
} catch (error) {
console .error ('Job failed:' , error)
}
}
this .processing = 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' })
}
Logging & Monitoring
Structured Logging 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 })
}
}
Remember : Backend patterns enable scalable, maintainable server-side applications. Choose patterns that fit your complexity level.