A quick learning spike around App Router and a POC of Code Organization.
https://www.loom.com/share/041f7211d7c440f78229d86d97e7f041?sid=71dcec3a-bda7-4390-bb20-508d541e5997
- create a
.env.development.local
- create a
.env.test.local
- Add
DATABASE_URL
for migrations - run
npm run db:setup
npm run dev
Open http://localhost:3000 with your browser to see the result.
This project uses next/font
to automatically optimize and load Inter, a custom Google Font.
Navigate from the generic Next Hello, World page to /posts
If you ran npm run db:seed
(or setup) you should see Posts here.
- Click "+ create Post" and try out Form Validations and Submitting.
- See your new post in /posts w/ a Toast Message
- Next.js (APP ROUTER)
- Prisma
- TailwindCSS
- Jest
- Playwright
✔ What is your project named? … bloggy-blog
✔ Would you like to use TypeScript? … Yes
✔ Would you like to use ESLint? … Yes
✔ Would you like to use Tailwind CSS? … Yes
✔ Would you like to use src/
directory? … Yes
✔ Would you like to use App Router? (recommended) … Yes
✔ Would you like to customize the default import alias (@/*)? … No
- Updated
.eslintrc.json
{
"extends": [
"next/core-web-vitals",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended"
],
"rules": {
"no-console": ["warn"],
"prettier/prettier": ["error", { "endOfLine": "auto" }],
"@typescript-eslint/no-unused-vars": [
"error",
{ "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }
]
},
"parser": "@typescript-eslint/parser",
"ignorePatterns": [
"**/node_modules/**",
"**/dist/**",
"**/build/**",
"**/coverage/**"
]
}
- added a
.prettierrc
file
{
"trailingComma": "all",
"tabWidth": 2,
"singleQuote": true,
"bracketSpacing": true
}
npm install prisma dotenv-cli ts-node --save-dev
npx prisma init --datasource-provider postgresql
- Create/Add a .gitignore
- Add .env.*.local
- Update the .local env with a real
DATABASE_URL
Note: a few of these are for later... but might as well copy now.
"scripts": {
"dev": "next dev",
"start": "next start",
"build": "next build",
"build:prisma": "prisma generate",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" ",
"db:migrate": "npm run withEnv:dev prisma migrate dev",
"db:reset": "npm run withEnv:dev prisma migrate reset",
"db:deploy": "prisma migrate deploy",
"db:seed": "npm run withEnv:dev prisma db:seed",
"lint:fix": "npm run lint --fix",
"test": "npm run withEnv:test jest --runInBand",
"test:cov": "npm run withEnv:test jest --runInBand --coverage",
"typecheck": "tsc --noEmit",
"withEnv": "dotenv -c --",
"withEnv:dev": "dotenv -c development --",
"withEnv:test": "dotenv -c test --"
},
model Post {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
title String
content String?
published Boolean @default(false)
}
npm run db:migrate
In the package.json add
"prisma": {
"seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seeds.ts"
},
In /prisma
add a seeds.ts
file and create some dummy seeds
We'll need to connect to services globally for things like Prisma. Lets set up the shared utils here... src/services
- Create a new file
/src/services/prisma.ts
import { PrismaClient } from '@prisma/client';
declare const globalThis: {
prisma?: PrismaClient;
};
export let prisma: PrismaClient;
if (process.env.NODE_ENV !== 'development') {
prisma = new PrismaClient();
} else {
if (!globalThis['prisma']) {
globalThis['prisma'] = new PrismaClient();
}
prisma = globalThis['prisma'];
}
export async function disconnect() {
await prisma.$disconnect();
return true;
}
export async function connect() {
await prisma.$connect();
return true;
}
the src/components
folder should be used for UI Components only.
This would be your "Storybook" folder of building blocks.
We keep Page Specific components such as a useForm
with in the App Router folder itself.
See app/posts/new
run npx shadcn-ui@latest init
- ✔ Would you like to use TypeScript (recommended)? … yes ✔ Which style would you like to use? › Default ✔ Which color would you like to use as base color? › Slate ✔ Where is your global CSS file? … src/app/globals.css ✔ Would you like to use CSS variables for colors? … no ✔ Where is your tailwind.config.js located? … tailwind.config.ts ✔ Configure the import alias for components: … @/components ✔ Configure the import alias for utils: … @/components/utils // keep within src/components ✔ Are you using React Server Components? … yes ✔ Write configuration to components.json. Proceed? … yes
npx shadcn-ui@latest add button
npx shadcn-ui@latest add toast
Layouts are just that - a general page layout. Depending you may not need this file as the root layout may suffice.
This is where the magic starts to happen. Page is a Server Driven component that will make fetch calls to pass data down to "use client" components. This "Page" is the main driver.
Any React Client Business happens here... See app/posts/new/CreatePostForm;
Need State/UseEffect it will be a "use client"
component.
For Forms we need to tap into Server Actions and shared Types
** Note: for the RETURN type we cannot throw
an error.
Instead we've created a generic type to leverage for {data, error}
returns.
Here, the client can decide what to do next.
See: Server Types AppServerResponse and an example use with CreatePostResponse
Anything that would be "use server"
or server util-y is found here.
Anything data related, and/or business logic related should be happening here.
Client Components/Pages are simply waiting on the data...
Server Actions
Zod, Prisma, and Type-y Types. this is Full Stack so Prisma is fine here. Types
They each have their own folder under /src
-- Lets try not to scatter these around the app.
Folder Structure similar to Types, Server, etc. if needed.
@tailwind/forms
,@tailwind/typography
react-hook-form
and@hookform/resolvers
zod
npx shadcn-ui@latest add button
ANDnpx shadcn-ui@latest add toast
- This added a handful of
@radix-ui/*
,clsx
, andlucide-react
- This added a handful of
- Husky
- Jest
- Playwright
- CI