See @README.md for project overview and @package.json
docker compose up- Running this starts the server and runs npm run devnpm run dev- Start development server with Turbopacknpm run start- Start production servernpm run build- Build production version
npm run check- Run all checks (types, lint, format, tests)npm run check:types- TypeScript type checkingnpm run check:lint- ESLint checkingnpm run check:format- Prettier format checkingnpm run test- Run all tests oncenpm run test:watch- Run tests in watch modenpm run format- Format code with Prettier
npm run db:generate- Generate Drizzle migrationsnpm run db:migrate- Run database migrationsnpm run db:clear- Clear all database datanpm run db:fresh- Clear database and run fresh migrationsnpm run db:production:migrate- Run production migrations. AI Models should NEVER DO THIS.
- Framework: Next.js 15 with App Router
- Database: PostgreSQL with Drizzle ORM
- Authentication: NextAuth.js v5 with email magic links
- Styling: Tailwind CSS with shadcn/ui components
- Testing: Vitest
- Payments: Stripe integration
src/app/- Next.js App Router pages and API routes(product)/- Product pages (requires auth)(site)/- Public marketing/auth pagesapi/- REST API endpoints with nested structure
src/components/- React components organized by purpose:common/- Shared business logic componentsmagicui/- UI animation componentsstructure/- Layout components (header, footer, navigation)ui/- shadcn/ui base components
src/lib/- Utility functions and shared logicsrc/middleware/- Request middleware (auth, API key validation, body parsing)src/schema/- Database schema and migrations
Uses Drizzle ORM with PostgreSQL. A full table structure can be found in src/schema/schema.ts
- Generate migrations with
npm run db:generate - If you need a blank or custom migration, you can generate it with the command
npx drizzle-kit generate --custom --name=some-name
- Session Auth: NextAuth.js with magic link email authentication
- API Auth: Custom API key system with encrypted storage for access to programmatic endpoints.
- Middleware:
withAuthfor session-protected routes,withApiKeyfor API endpoints - Development: Uses console logging for magic links instead of email sending
Pages must use semantic <section> blocks with consistent layout:
<section className="section section-padding">
<div className="content">
<!-- content here -->
</div>
</section>- When using a section tag you MUST apply section and section-padding first before other styles. These take care of the padding and spacing.
- When placing a div inside a section tag it MUST have either
contentorcontent-wideclass as this takes care of the maximum width. - When applying mobile optimization styles, you cannot duplicate components. You should be using conditional rendering based on tailwind breakpoints like this:
<div className="grid grid-cols-1 md:grid-cols-2"> {/* Some card */} </div>This codebase uses a custom type-safe API request pattern. When implementing new API endpoints, follow this pattern for consistency and type safety.
- Create API Schema (
/api/endpoint/schema.ts):
import { z } from "zod";
import type { APISchema } from "@/schema/types";
const schema = {
url: "/api/endpoint-name",
// Define request body schema (use z.undefined() for GET)
request: z.object({
}),
// Define response body schema
response: z.object({
}),
} satisfies APISchema;
export default schema;- Implement Route Handler (
/api/endpoint/route.ts):
import schema from "./schema";
import { NextRouteContext, RequestHandler } from "@/middleware/types";
import { withAuth } from "@/middleware/withAuth";
import { withBody } from "@/middleware/withBody"; // For POST/PUT
// GET request
export const GET: RequestHandler<NextRouteContext> = withAuth(async (_, context) => {
// Implementation...
const response = schema.response.parse(result);
return NextResponse.json(response);
});
// POST request with body validation
export const POST = withAuth(
withBody(schema, async (_, context) => {
const { body } = context; // Typed and validated
// Implementation...
const response = schema.response.parse(result);
return NextResponse.json(response);
})
);- Client-Side Usage:
import requests from "@/lib/requests";
import endpointSchema from "@/app/api/endpoint/schema";
// GET request
const data = await requests.get(endpointSchema);
// POST request
const result = await requests.post(endpointSchema, requestBody);- Use
z.undefined()for GET request schemas (no body) - Always validate responses with
schema.response.parse()before sending them to the client - Import schema in both route handler and client code
- Use middleware (
withAuth,withBody,withApiKey) for common functionality; These can be chained together. - Export default schema from schema files
- Use
camelCaseexcept for database fields which aresnake_case(due to SQL case-sensitivity) - Prefer optional chaining:
address?.postalCodeoveraddress && address.postalCode
- Never use
any- useunknowninstead if type is unknown - Avoid
as unknown as Type- indicates wrong approach - Avoid
as Type- also indicates wrong approach
When writing site contents, respect eslintreact/no-unescaped-entities. This is a requirement. Escape chars like ' should be used instead of '.
If you are creating a new ShadCN component you MUST install it with an npx command (like npx shadcn@latest add badge). You should NEVER write a ShadCN component from scratch, it should ALWAYS be installed.
Never git push