Senior fullstack
Koleksi 20 Claude Skills siap pakai untuk pengembangan SaaS, web modern, dan praktik rekayasa perangkat lunak tingkat lanjut.
npx -y skills add roedyrustam/claudevibeskills --skill senior-fullstackAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Expert fullstack architecture skill covering advanced backend patterns, infrastructure, testing, and system design. Use this skill whenever the user is working on fullstack architecture decisions, API design, background job queues, caching strategies, database connection pooling, CI/CD pipelines, Docker containerization, end-to-end testing with Playwright, WebSocket real-time features, or deployment configurations. Trigger on mentions of Redis, BullMQ, Prisma, Drizzle, PostgreSQL, Docker, GitHub Actions, connection pooling, rate limiting, Playwright, or any backend/infra topic that goes beyond simple CRUD. Also trigger for performance bottlenecks, scalability concerns, or production readiness reviews.
SKILL.md
16.3 KB, as published. Nobody here has run it
Senior Fullstack Engineer Skill
Advanced architectural patterns, infrastructure, testing, and production-grade system design.
Core Stack
| Layer | Technology |
|---|---|
| Framework | Next.js 15 (App Router) |
| ORM | Drizzle ORM or Prisma |
| Database | PostgreSQL (Supabase / Neon / RDS) |
| Cache / Queue | Redis + BullMQ |
| Auth | Clerk or NextAuth v5 |
| Payments | Stripe |
| Testing | Playwright (E2E) + Vitest (unit) |
| CI/CD | GitHub Actions |
| Containers | Docker + Docker Compose |
| Monitoring | Sentry + OpenTelemetry |
Database Layer
Drizzle ORM (Preferred — Type-Safe, Lightweight)
// lib/db/schema.ts
import { pgTable, text, timestamp, uuid, boolean, index } from 'drizzle-orm/pg-core'
export const users = pgTable('users', {
id: uuid('id').primaryKey().defaultRandom(),
clerkId: text('clerk_id').notNull().unique(),
email: text('email').notNull().unique(),
name: text('name').notNull(),
createdAt: timestamp('created_at').notNull().defaultNow(),
updatedAt: timestamp('updated_at').notNull().defaultNow(),
}, (table) => ({
clerkIdIdx: index('users_clerk_id_idx').on(table.clerkId),
emailIdx: index('users_email_idx').on(table.email),
}))
export const posts = pgTable('posts', {
id: uuid('id').primaryKey().defaultRandom(),
userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
title: text('title').notNull(),
slug: text('slug').notNull().unique(),
content: text('content'),
published: boolean('published').notNull().default(false),
publishedAt: timestamp('published_at'),
createdAt: timestamp('created_at').notNull().defaultNow(),
}, (table) => ({
userIdIdx: index('posts_user_id_idx').on(table.userId),
slugIdx: index('posts_slug_idx').on(table.slug),
}))
Database Connection Pooling
// lib/db/index.ts
import { drizzle } from 'drizzle-orm/node-postgres'
import { Pool } from 'pg'
import * as schema from './schema'
// Single pool instance — critical for connection pooling
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 20, // Max connections
idleTimeoutMillis: 30_000, // Close idle connections after 30s
connectionTimeoutMillis: 2_000,
})
export const db = drizzle(pool, { schema })
// For serverless (Neon, Supabase with pgBouncer)
// Use neon-serverless driver instead:
// import { neon } from '@neondatabase/serverless'
// const sql = neon(process.env.DATABASE_URL!)
// export const db = drizzle(sql, { schema })
Repository Pattern
// lib/db/repositories/post.repository.ts
import { db } from '@/lib/db'
import { posts, users } from '@/lib/db/schema'
import { eq, desc, and, sql } from 'drizzle-orm'
export class PostRepository {
async findPublished(limit = 10, offset = 0) {
return db
.select({
id: posts.id,
title: posts.title,
slug: posts.slug,
publishedAt: posts.publishedAt,
author: { name: users.name },
})
.from(posts)
.innerJoin(users, eq(posts.userId, users.id))
.where(eq(posts.published, true))
.orderBy(desc(posts.publishedAt))
.limit(limit)
.offset(offset)
}
async findBySlug(slug: string) {
const [post] = await db
.select()
.from(posts)
.where(eq(posts.slug, slug))
.limit(1)
return post ?? null
}
async create(data: typeof posts.$inferInsert) {
const [post] = await db.insert(posts).values(data).returning()
return post
}
async incrementView(id: string) {
await db
.update(posts)
.set({ viewCount: sql`${posts.viewCount} + 1` })
.where(eq(posts.id, id))
}
}
export const postRepository = new PostRepository()
Redis Caching
Setup & Client
// lib/redis.ts
import { Redis } from 'ioredis'
let redis: Redis
if (process.env.NODE_ENV === 'production') {
redis = new Redis(process.env.REDIS_URL!, {
maxRetriesPerRequest: 3,
enableReadyCheck: false,
lazyConnect: true,
})
} else {
// Singleton in dev to prevent hot-reload creating new connections
const globalForRedis = globalThis as { redis?: Redis }
globalForRedis.redis ??= new Redis(process.env.REDIS_URL!)
redis = globalForRedis.redis
}
export { redis }
Cache-Aside Pattern
// lib/cache.ts
import { redis } from './redis'
export async function cached<T>(
key: string,
fn: () => Promise<T>,
ttlSeconds = 60
): Promise<T> {
const cached = await redis.get(key)
if (cached) return JSON.parse(cached) as T
const result = await fn()
await redis.setex(key, ttlSeconds, JSON.stringify(result))
return result
}
export function cacheKey(...parts: string[]) {
return parts.join(':')
}
// Usage
const posts = await cached(
cacheKey('posts', 'published', page.toString()),
() => postRepository.findPublished(10, offset),
300 // 5 minutes
)
Cache Invalidation Strategy
// Granular tag-based invalidation
export async function invalidatePostCache(postId: string, slug: string) {
const keys = await redis.keys(`posts:*`)
if (keys.length > 0) await redis.del(...keys)
await redis.del(`post:${slug}`, `post:${postId}`)
}
Background Jobs with BullMQ
Queue Setup
// lib/queues/index.ts
import { Queue, Worker, QueueEvents } from 'bullmq'
import { redis } from '@/lib/redis'
const connection = { connection: redis }
// Define queues
export const emailQueue = new Queue('email', connection)
export const imageQueue = new Queue('image-processing', connection)
export const webhookQueue = new Queue('webhooks', connection)
// Job types
export type EmailJobData = {
to: string
subject: string
template: 'welcome' | 'reset-password' | 'invoice'
variables: Record<string, string>
}
Worker
// workers/email.worker.ts
import { Worker } from 'bullmq'
import { redis } from '@/lib/redis'
import { sendEmail } from '@/lib/email'
import type { EmailJobData } from '@/lib/queues'
export const emailWorker = new Worker<EmailJobData>(
'email',
async (job) => {
const { to, subject, template, variables } = job.data
await job.updateProgress(10)
const html = await renderEmailTemplate(template, variables)
await job.updateProgress(50)
await sendEmail({ to, subject, html })
await job.updateProgress(100)
return { sent: true, to }
},
{
connection: { connection: redis },
concurrency: 5,
limiter: { max: 100, duration: 60_000 }, // 100 emails/min
}
)
emailWorker.on('completed', (job, result) => {
console.log(`Email job ${job.id} completed`, result)
})
emailWorker.on('failed', (job, err) => {
console.error(`Email job ${job?.id} failed:`, err)
})
Enqueue from Server Actions
// app/actions/auth.actions.ts
'use server'
import { emailQueue } from '@/lib/queues'
export async function sendWelcomeEmail(userId: string) {
const user = await userRepository.findById(userId)
if (!user) return
await emailQueue.add(
'welcome-email',
{
to: user.email,
subject: 'Welcome!',
template: 'welcome',
variables: { name: user.name },
},
{
attempts: 3,
backoff: { type: 'exponential', delay: 5000 },
removeOnComplete: 100, // Keep last 100 completed
removeOnFail: 500,
}
)
}
API Design (Route Handlers)
Typed Route Handler
// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { auth } from '@clerk/nextjs/server'
import { z } from 'zod'
import { postRepository } from '@/lib/db/repositories/post.repository'
const createPostSchema = z.object({
title: z.string().min(1).max(200),
content: z.string().optional(),
published: z.boolean().default(false),
})
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url)
const page = Math.max(1, Number(searchParams.get('page') ?? 1))
const limit = Math.min(50, Number(searchParams.get('limit') ?? 10))
const posts = await postRepository.findPublished(limit, (page - 1) * limit)
return NextResponse.json({ data: posts, page, limit })
}
export async function POST(request: NextRequest) {
const { userId } = await auth()
if (!userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const body = await request.json()
const parsed = createPostSchema.safeParse(body)
if (!parsed.success) {
return NextResponse.json(
{ error: 'Validation failed', details: parsed.error.flatten() },
{ status: 422 }
)
}
const post = await postRepository.create({ ...parsed.data, userId })
return NextResponse.json({ data: post }, { status: 201 })
}
Rate Limiting Middleware
// middleware.ts
import { NextResponse, type NextRequest } from 'next/server'
import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis'
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, '10 s'),
analytics: true,
})
export async function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/api/')) {
const ip = request.ip ?? '127.0.0.1'
const { success, limit, reset, remaining } = await ratelimit.limit(ip)
if (!success) {
return NextResponse.json(
{ error: 'Too many requests' },
{
status: 429,
headers: {
'X-RateLimit-Limit': limit.toString(),
'X-RateLimit-Remaining': remaining.toString(),
'X-RateLimit-Reset': reset.toString(),
},
}
)
}
}
}
export const config = {
matcher: '/api/:path*',
}
Testing
Playwright E2E
// tests/e2e/auth.spec.ts
import { test, expect } from '@playwright/test'
test.describe('Authentication flow', () => {
test('user can sign up and access dashboard', async ({ page }) => {
await page.goto('/sign-up')
await page.fill('[name="email"]', '[email protected]')
await page.fill('[name="password"]', 'SecurePass123!')
await page.click('[type="submit"]')
await expect(page).toHaveURL('/dashboard')
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible()
})
test('protected routes redirect unauthenticated users', async ({ page }) => {
await page.goto('/dashboard')
await expect(page).toHaveURL(/\/sign-in/)
})
})
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
testDir: './tests/e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [['html'], ['github']],
use: {
baseURL: process.env.BASE_URL ?? 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'Mobile Safari', use: { ...devices['iPhone 14'] } },
],
webServer: {
command: 'pnpm dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
})
Vitest Unit Tests
// lib/__tests__/cache.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { cached } from '@/lib/cache'
vi.mock('@/lib/redis', () => ({
redis: {
get: vi.fn(),
setex: vi.fn(),
},
}))
describe('cached()', () => {
beforeEach(() => vi.clearAllMocks())
it('returns cached value when cache hit', async () => {
const { redis } = await import('@/lib/redis')
vi.mocked(redis.get).mockResolvedValue(JSON.stringify({ id: 1 }))
const fn = vi.fn().mockResolvedValue({ id: 1 })
const result = await cached('test-key', fn)
expect(result).toEqual({ id: 1 })
expect(fn).not.toHaveBeenCalled()
})
it('calls fn and caches result on miss', async () => {
const { redis } = await import('@/lib/redis')
vi.mocked(redis.get).mockResolvedValue(null)
const fn = vi.fn().mockResolvedValue({ id: 2 })
const result = await cached('test-key', fn, 60)
expect(result).toEqual({ id: 2 })
expect(redis.setex).toHaveBeenCalledWith('test-key', 60, JSON.stringify({ id: 2 }))
})
})
Docker & CI/CD
Multi-Stage Dockerfile
# Dockerfile
FROM node:20-alpine AS base
RUN apk add --no-cache libc6-compat
WORKDIR /app
RUN npm install -g pnpm
FROM base AS deps
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
FROM base AS builder
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN pnpm build
FROM base AS runner
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]
GitHub Actions CI Pipeline
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: testdb
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Type check
run: pnpm tsc --noEmit
- name: Lint
run: pnpm lint
- name: Unit tests
run: pnpm test
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb
- name: Run migrations
run: pnpm db:migrate
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb
- name: Build
run: pnpm build
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb
e2e:
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- name: Install Playwright browsers
run: pnpm playwright install --with-deps chromium
- name: Run E2E tests
run: pnpm playwright test
env:
BASE_URL: http://localhost:3000
- uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report
path: playwright-report/
Key Architectural Rules
- Repository pattern for all DB access — never raw queries in route handlers or Server Actions
- Connection pool as singleton — one
Poolinstance, not one per request - BullMQ for anything async — email, image processing, webhooks, notifications
- Redis cache-aside — cache at the repository layer, not the component layer
- Zod validation at every API boundary — route handlers AND Server Actions
- Rate limit all public APIs — use Upstash Redis for serverless-safe limiting
- Multi-stage Docker builds — never ship dev dependencies to production
- Test pyramid: unit (Vitest) > integration > E2E (Playwright)
- CI gates on type check + lint + unit tests before E2E
- Never commit secrets — use environment variables, validate at startup