Coding Conventions
Code style, naming patterns, and best practices for Reqcore development. Covers Vue components, API routes, Drizzle queries, and more.
Coding Conventions
This guide covers the coding standards and patterns used throughout Reqcore. Following these conventions ensures consistency and prevents common mistakes.
TypeScript
Reqcore is fully typed. Nuxt 4 provides separate TypeScript contexts for app/ and server/:
- Server utilities are not available in
app/code (and vice versa) - Use
shared/for types and functions needed in both contexts - API response types are inferred automatically by
useFetch
Vue Components
File Organization
<script setup lang="ts">
// 1. Imports (framework, libraries, types)
import { Briefcase, ArrowRight } from 'lucide-vue-next'
// 2. Page meta & SEO
definePageMeta({ layout: 'dashboard', middleware: ['auth'] })
useSeoMeta({ title: 'Jobs', description: '...' })
// 3. Reactive state & composables
const { data: jobs } = await useFetch('/api/jobs', {
headers: useRequestHeaders(['cookie']),
})
// 4. Computed properties
const openJobs = computed(() =>
jobs.value?.filter(j => j.status === 'open') ?? []
)
// 5. Methods
function handleCreate() { /* ... */ }
</script>
<template>
<!-- Component template -->
</template>
Naming
| Item | Convention | Example |
|---|---|---|
| Components | PascalCase | PipelineCard.vue, OrgSwitcher.vue |
| Composables | camelCase with use prefix | useJobs(), useCandidate() |
| Pages | kebab-case | application-form.vue |
| Layouts | kebab-case | dashboard.vue |
API Routes
File Naming
server/api/jobs/index.get.ts → GET /api/jobs
server/api/jobs/index.post.ts → POST /api/jobs
server/api/jobs/[id].get.ts → GET /api/jobs/:id
server/api/jobs/[id].patch.ts → PATCH /api/jobs/:id
server/api/jobs/[id].delete.ts → DELETE /api/jobs/:id
Handler Pattern
export default defineEventHandler(async (event) => {
// 1. Authenticate
const { session } = await requireAuth(event)
const orgId = session.activeOrganizationId
// 2. Validate input
const body = await readValidatedBody(event, schema.parse)
// 3. Query (always scoped by orgId)
const result = await db
.select()
.from(table)
.where(eq(table.organizationId, orgId))
// 4. Return data
return result
})
Error Handling
// ✅ Correct — throws proper HTTP error
throw createError({ statusCode: 404, message: 'Job not found' })
// ❌ Wrong — returns 200 with error body
return { error: 'Job not found' }
Database Queries
Always Scope by Organization
// ✅ Every query MUST include organizationId
const jobs = await db
.select()
.from(job)
.where(eq(job.organizationId, orgId))
// ❌ Never query without org scope
const jobs = await db.select().from(job)
Use Drizzle Operators
import { eq, and, desc } from 'drizzle-orm'
const results = await db
.select()
.from(application)
.where(
and(
eq(application.organizationId, orgId),
eq(application.jobId, jobId),
)
)
.orderBy(desc(application.createdAt))
CSS & Styling
Tailwind CSS v4
- Use utility classes — avoid custom CSS
- Theme tokens are defined in
app/assets/css/main.cssvia@theme - The
brand-*color scale maps to the primary color
Dark Theme Pages
Public pages (landing, blog, roadmap, docs) use a dark theme:
useHead({
bodyAttrs: {
style: 'background-color: #09090b;',
},
})
Common dark theme patterns:
- Background:
bg-[#09090b] - Text:
text-white,text-white/50,text-white/40 - Borders:
border-white/[0.06] - Cards:
bg-white/[0.02], hover:bg-white/[0.04] - Glow:
blur-[120px]with brand colors
Environment Variables
// ✅ Use the validated env utility
import { env } from '~/server/utils/env'
const dbUrl = env.DATABASE_URL
// ❌ Never access process.env directly in server code
const dbUrl = process.env.DATABASE_URL
Next Steps
- Development Setup — Get your environment ready
- Architecture Overview — Understand the codebase
- Security — Security invariants to never break