Docs/getting started/Quick Start

Quick Start

Let's build a simple feature to understand how NextShip works.

Understanding the Stack

NextShip uses these core technologies:

LayerTechnologyPurpose
FrontendReact 19 + Next.js 15UI and routing
StylingTailwind CSS v4 + shadcn/uiComponents and styling
APItRPC + Server ActionsType-safe API calls
DatabaseDrizzle ORM + PostgreSQLData persistence
AuthBetter AuthUser authentication

Adding a New Page

  1. Create a new file in src/app/[locale]/(dashboard)/:
// src/app/[locale]/(dashboard)/my-page/page.tsx
import { setRequestLocale } from "next-intl/server";
 
export default async function MyPage({
  params,
}: {
  params: Promise<{ locale: string }>;
}) {
  const { locale } = await params;
  setRequestLocale(locale);
 
  return (
    <div className="container py-8">
      <h1 className="text-2xl font-bold">My New Page</h1>
    </div>
  );
}

Adding a Database Table

  1. Define the schema in src/lib/db/schema.ts:
export const projects = pgTable("projects", {
  id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
  name: text("name").notNull(),
  userId: text("user_id").references(() => users.id),
  createdAt: timestamp("created_at").defaultNow(),
});
  1. Push the changes:
pnpm db:push

Creating a tRPC Router

  1. Add a new router in src/server/trpc/routers/:
// src/server/trpc/routers/project.ts
import { z } from "zod";
import { createTRPCRouter, protectedProcedure } from "../trpc";
import { projects } from "@/lib/db/schema";
 
export const projectRouter = createTRPCRouter({
  list: protectedProcedure.query(async ({ ctx }) => {
    return ctx.db
      .select()
      .from(projects)
      .where(eq(projects.userId, ctx.user.id));
  }),
 
  create: protectedProcedure
    .input(z.object({ name: z.string().min(1) }))
    .mutation(async ({ ctx, input }) => {
      return ctx.db.insert(projects).values({
        name: input.name,
        userId: ctx.user.id,
      });
    }),
});
  1. Register in the root router (src/server/trpc/router.ts).

Using the API in Components

"use client";
 
import { trpc } from "@/lib/trpc/client";
 
export function ProjectList() {
  const { data: projects, isLoading } = trpc.project.list.useQuery();
 
  if (isLoading) return <div>Loading...</div>;
 
  return (
    <ul>
      {projects?.map((project) => (
        <li key={project.id}>{project.name}</li>
      ))}
    </ul>
  );
}

Next Steps