Quick Start Guide

Get started with ShipKit in minutes

Quick Start Guide

This guide will help you set up a new ShipKit project and understand its core features. Follow these steps to create a production-ready application foundation.

Prerequisites

Ensure you have the following installed:

Creating Your Project

  1. Create a new ShipKit project:
pnpm create shipkit-app@latest my-app
cd my-app
  1. Install dependencies:
pnpm install
  1. Configure your environment:
cp .env.example .env
  1. Update your .env file with required credentials:
# Core Auth
AUTH_URL="http://localhost:3000"            # Base URL for authentication
AUTH_SECRET="generate_a_secure_secret_here" # openssl rand -base64 32

# OAuth Providers (Optional)
# GitHub: https://github.com/settings/developers
AUTH_GITHUB_ID=""     # GitHub OAuth App Client ID
AUTH_GITHUB_SECRET="" # GitHub OAuth App Client Secret

# Database
DATABASE_URL="postgresql://postgres:password@localhost:5432/shipkit"
DB_PREFIX="sk"        # Prefix for database tables

# Email (Resend: https://resend.com)
RESEND_API_KEY="re_123456789" # Get from Resend dashboard

# CMS (Payload)
PAYLOAD_SECRET="generate_another_secret_here" # openssl rand -base64 32

# Node Environment
NODE_ENV="development" # development, test, or production
  1. Initialize your database:
# Start PostgreSQL (if using Docker)
pnpm db:start

# Generate database schema
pnpm db:generate

# Push schema to database
pnpm db:push

# Optional: Seed with sample data
pnpm db:seed
  1. Start the development server:
# Start with experimental features (recommended)
pnpm dev

# Start without experimental features
pnpm dev.legacy

# Start with HTTPS (for testing secure features)
pnpm dev.https

Your application will be running at http://localhost:3000

Project Structure

ShipKit follows a domain-driven structure:

my-app/
├── src/
│   ├── app/              # Next.js App Router pages and layouts
│   │   ├── (auth)/      # Authentication routes
│   │   ├── (marketing)/ # Public marketing pages
│   │   ├── api/         # API routes
│   │   └── dashboard/   # Protected dashboard routes
│   ├── components/      # React components
│   │   ├── ui/         # Shadcn/UI components
│   │   ├── forms/      # Form components
│   │   └── shared/     # Shared components
│   ├── server/         # Server-side code
│   │   ├── actions/    # Server actions
│   │   ├── services/   # Business logic services
│   │   ├── db/         # Database schema and queries
│   │   ├── utils/      # Server utilities
│   │   └── middleware/ # Custom middleware
│   ├── lib/            # Utility functions
│   │   ├── utils/     # Helper functions
│   │   ├── hooks/     # React hooks
│   │   └── config/    # Configuration files
│   ├── types/         # TypeScript types
│   └── workers/       # Web workers
├── public/            # Static assets
├── tests/            # Test files
│   ├── e2e/         # Playwright tests
│   └── unit/        # Vitest tests
├── scripts/         # Maintenance scripts
└── .env.example     # Environment variables template

Core Features

Authentication

ShipKit uses NextAuth v5 with JWT sessions and activity logging:

// src/server/auth.config.ts
import type { NextAuthConfig } from "next-auth";
import { ActivityLogger } from "./utils/activity-logger";

export const authOptions: NextAuthConfig = {
  providers,
  callbacks: {
    async signIn({ user, account }) {
      await ActivityLogger.auth.logLogin(null, {
        details: `User signed in via ${account?.provider}`,
        metadata: {
          provider: account?.provider,
          email: user.email,
        },
      });
      return true;
    },
    session({ session, token }) {
      if (token) {
        session.user.id = token.id as string;
        session.user.name = token.name as string | null;
        session.user.bio = token.bio as string | null;
        session.user.githubUsername = token.githubUsername as string | null;
        session.user.theme = token.theme as "light" | "dark" | "system";
        session.user.emailNotifications = token.emailNotifications as boolean;
      }
      return session;
    }
  }
};

Database Operations

Type-safe queries with Drizzle ORM:

// src/server/db/schema.ts
import { relations, sql } from "drizzle-orm";
import { pgTableCreator, varchar, timestamp, text, boolean } from "drizzle-orm/pg-core";

const createTable = pgTableCreator((name) => `${env.DB_PREFIX}_${name}`);

export const users = createTable("user", {
  id: varchar("id", { length: 255 })
    .notNull()
    .primaryKey()
    .$defaultFn(() => crypto.randomUUID()),
  name: varchar("name", { length: 255 }),
  email: varchar("email", { length: 255 }).notNull(),
  emailVerified: timestamp("email_verified", {
    mode: "date",
    withTimezone: true,
  }).default(sql`CURRENT_TIMESTAMP`),
  githubUsername: varchar("github_username", { length: 255 }),
  role: varchar("role", { length: 50 }).default("user").notNull(),
  bio: text("bio"),
  theme: varchar("theme", { length: 20 }).default("system"),
  emailNotifications: boolean("email_notifications").default(true),
  createdAt: timestamp("created_at", { withTimezone: true })
    .default(sql`CURRENT_TIMESTAMP`)
    .notNull(),
  updatedAt: timestamp("updated_at", { withTimezone: true }).$onUpdate(
    () => new Date(),
  ),
});

// Type-safe relations
export const usersRelations = relations(users, ({ many }) => ({
  accounts: many(accounts),
}));

Server Actions

Type-safe form handling with Zod validation:

// src/server/actions/projects.ts
'use server'

import { z } from "zod";
import { db } from "@/server/db";
import { projects } from "@/server/db/schema";

const createProjectSchema = z.object({
  name: z.string().min(1, "Project name is required"),
  teamId: z.string().min(1, "Team ID is required"),
});

export async function createProject(input: z.infer<typeof createProjectSchema>) {
  const validated = createProjectSchema.parse(input);

  return await db.insert(projects).values({
    name: validated.name,
    teamId: validated.teamId,
  }).returning();
}

UI Components

Build accessible interfaces with Shadcn/UI and React Server Components:

import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { useFormStatus } from "react-dom";

export function LoginForm() {
  const { pending } = useFormStatus();

  return (
    <form className="space-y-4">
      <Input
        type="email"
        name="email"
        placeholder="Email"
        disabled={pending}
        required
      />
      <Button
        type="submit"
        disabled={pending}
        className="w-full"
      >
        {pending ? 'Logging in...' : 'Login'}
      </Button>
    </form>
  );
}

Development Workflow

Running Tests

# Unit tests
pnpm test

# E2E tests
pnpm test:e2e

# Test coverage
pnpm test:coverage

Database Management

# Generate migrations
pnpm db:generate

# Apply migrations
pnpm db:push

# Reset database
pnpm db:reset

# Start database
pnpm db:start

# Seed data
pnpm db:seed

Code Quality

# Type checking
pnpm typecheck

# Linting
pnpm lint

# Format code
pnpm format

# Check code style
pnpm check

Next Steps

  1. Development Guide - Learn development workflows
  2. Core Concepts - Understand the architecture
  3. API Reference - Explore the API
  4. UI Components - Browse available components