Remixã触ã£ã¦ã¿ããã¨æã£ã¦ããããReact Router v7ã«çµ±åããã¦ãã¾ãã¾ããã
ãããªReact Routerã§ããã¸ã§ã¯ããä½æããPrismaã¨Vitestãå°å
¥ãã¦ãã¹ããæ¸ãå§ããæºåã«ã¤ãã¦æ¸ãã¾ããã
React Routerã§ããã¸ã§ã¯ãä½æ
ããã¸ã§ã¯ãã®ä½æã¯ä»¥ä¸ãå®è¡ãã¾ãã
> npx create-react-router@latest my-react-router-app
Need to install the following packages:
create-react-router@7.0.2
Ok to proceed? (y)
create-react-router v7.0.2
â¼ Directory: Using my-react-router-app as project directory
â¼ Using default template See https://github.com/remix-run/react-router-templates for more
â Template copied
git Initialize a new git repository?
Yes
deps Install dependencies with npm?
No
â¼ Skipping install step. Remember to install dependencies after setup with npm install.
â Git initialized
done That's it!
Enter your project directory using cd ./my-react-router-app
Check out README.md for development and deploy instructions.
Join the community at https://rmx.as/discord
docker composeã§DBãç¨æ
ä»åã¯PostgreSQLã使ç¨ãã¾ãã
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
driver: local
Prismaå°å
¥
> pnpm i -D prisma
> npx prisma init --datasource-provider postgresql
ã¹ãã¼ãå®ç¾©
ããã©ã«ãã§ã¯Prismaã®ã¹ãã¼ãã§å®ç¾©ãããã¢ãã«åããã®ã¾ã¾ãã¼ãã«åã¨ãªãä½æããã¾ããã
ãã¾ã馴æã¿ããªãã®ã§å°æåã®è¤æ°å½¢ã§ä½æãããããã«@@map
ã§ãã¼ãã«åãæå®ãã¾ãã
www.prisma.io
// prisma/shema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
@@map("users")
id Int @id @default(autoincrement())
email String @unique
name String
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
@@map("posts")
id Int @id @default(autoincrement())
title String @db.VarChar(255)
content String @db.VarChar(255)
published Boolean @default(false)
user User @relation(fields: [userId], references: [id])
userId Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
éçºç¨ã¨ãã¹ãç¨ã®DBãããããä½æ
éçºç¨ã¨ãã¹ãç¨ã§DBãåãããã®ã§ãdotenvã§èªã¿è¾¼ãç°å¢å¤æ°ãåãæ¿ããæ¹æ³ãã¨ãã¾ãã
> pnpm i -D dotenv-cli
.env.developmentã.env.testãä½æããããããã«ç°å¢å¤æ°DATABASE_URL
ãå®ç¾©ãã¦ããã¾ãã
# .env.development
# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
DATABASE_URL="postgresql://postgres:password@localhost:5432/myapp_development"
# .env.test
DATABASE_URL="postgresql://postgres:password@localhost:5432/myapp_test"
package.json
ã®scriptã«DBã¸ã®ã¹ãã¼ãåæ ããã¤ã°ã¬ã¼ã·ã§ã³ã³ãã³ãã追å ãã¾ãã
dotenv-cli
ã«ããèªã¿è¾¼ãç°å¢å¤æ°ãæå®ãã¦å®è¡å
ãåãæ¿ããããã«ãã¦ãã¾ãã
追å å
容
- ã¹ãã¼ãå¤æ´å¾ãPrisma Clientã®æ´æ°ã»åçæãè¡ãããã®
npx prisma generate
ã³ãã³ããæå®
- ãã¤ã°ã¬ã¼ã·ã§ã³å®è¡ã»ãã¡ã¤ã«çæãè¡ã
npx prisma migrate dev
ã³ãã³ããæå®
- ååDBä½æãè¡ãéã¯
init
ãªãã·ã§ã³ãã¤ãã¦å®è¡ãã
- ãã¹ãç°å¢DBã«å¯¾ãã¦ã¯æ¬çªç°å¢ã¨åæ§ã«ãã¤ã°ã¬ã¼ã·ã§ã³ãã¡ã¤ã«åæ ã®ã¿è¡ãããã®ã§
npx prisma migrate deploy
ã³ãã³ãã使ç¨
// package.json
{
"name": "my-react-router-app",
"private": true,
"type": "module",
"scripts": {
"build": "react-router build",
"dev": "react-router dev",
"start": "react-router-serve ./build/server/index.js",
"typecheck": "react-router typegen && tsc --build --noEmit",
+ "prisma": "npx prisma generate",
+ "db:migrate": "dotenv -e .env.development -- npx prisma migrate dev",
+ "db:migrate:test": "dotenv -e .env.test -- npx prisma migrate deploy",
},
...
}
Seeding
prismaãã©ã«ãã«seed.tsãã¡ã¤ã«ã追å ããseedãå®ç¾©ãã¾ãã
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
const alice = await prisma.user.upsert({
where: { email: '[email protected]' },
update: {},
create: {
email: '[email protected]',
name: 'Alice',
posts: {
create: {
title: 'Check out Prisma with Next.js',
content: 'https://www.prisma.io/nextjs',
published: true,
},
},
},
});
const bob = await prisma.user.upsert({
where: { email: '[email protected]' },
update: {},
create: {
email: '[email protected]',
name: 'Bob',
posts: {
create: [
{
title: 'Follow Prisma on Twitter',
content: 'https://twitter.com/prisma',
published: true,
},
{
title: 'Follow Nexus on Twitter',
content: 'https://twitter.com/nexusgql',
published: true,
},
],
},
},
});
console.log({ alice, bob });
}
main()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
.tsãã¡ã¤ã«å®è¡ã®ããã«å¥étsx
ãã¤ã³ã¹ãã¼ã«ãã¦ããã¾ãã
(ããã¥ã¡ã³ãã§ã¯ts-node
ã使ç¨ãã¦ãã¾ããã.tsãã¡ã¤ã«ã®æ¡å¼µåããã¾ãèªèãããã¨ã©ã¼ã«ãªã)
github.com
// package.json
{
"name": "my-react-router-app",
"private": true,
"type": "module",
"scripts": {
"build": "react-router build",
"dev": "react-router dev",
"start": "react-router-serve ./build/server/index.js",
"typecheck": "react-router typegen && tsc --build --noEmit",
"prisma": "npx prisma generate",
"db:migrate": "dotenv -e .env.development -- npx prisma migrate dev",
"db:migrate:test": "dotenv -e .env.test -- npx prisma migrate deploy",
+ "db:seed": "dotenv -e .env.development -- npx prisma db seed",
+ "db:seed:test": "dotenv -e .env.test -- npx prisma db seed"
},
+ "prisma": {
+ "seed": "tsx prisma/seed.ts"
+ },
...
}
npm run db:seed
ã§å®è¡ãããmigrateå®è¡æã«seedãå®è¡ããã¾ãã
Vitestå°å
¥
Vitestã¨ä½µãã¦jsdom
ãtesting-library
å種ãã¤ã³ã¹ãã¼ã«ãã¾ãã
> pnpm i -D vitest jsdom @testing-library/react @testing-library/user-event @testing-library/jest-dom @testing-library/react-hooks
vite.config.tsã®è¨å®
React Routerã§ã¯ãã§ã«Viteã使ç¨ããã¦ããã®ã§vite.config.ts
ã¸è¨å®ãè¡ãã¾ãã
@testing-library/jest-dom
ã®ã«ã¹ã¿ã ãããã£ã¼ã使ç¨ããããã«å¥éè¨å®ãã¡ã¤ã«ãä½æãã¦setupFiles
ã¸ãã¹ãæå®ãã¾ãã
ã¾ããReactRouterã®Viteãã©ã°ã¤ã³ã¯ãã¹ãã§ã®ä½¿ç¨ãæ³å®ããã¦ããªããã(éçºãµã¼ãã¼ã¨æ¬çªãã«ãã§ã®ä½¿ç¨ã®ã¿ã®æ³å®)ãVitestã«ãããã¹ãå®è¡æã¯ä½¿ç¨ãããªãããã«ç°å¢å¤æ°VITESTã§å¶å¾¡ãã¾ãã
React Routerã®ããã¥ã¡ã³ãã®æ¹ã§è¦ã¤ããããªãã£ãã®ã§ãRemixã®ããã¥ã¡ã³ãã詳ããã¯åç
§ãã¦ãã ããã
remix.run
// vite.config.ts
+/// <reference types="vitest/config" />
import { reactRouter } from '@react-router/dev/vite';
import autoprefixer from 'autoprefixer';
import tailwindcss from 'tailwindcss';
import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
css: {
postcss: {
plugins: [tailwindcss, autoprefixer],
},
},
+ plugins: [!process.env.VITEST && reactRouter(), tsconfigPaths()],
+ test: {
+ globals: true,
+ environment: 'jsdom',
+ setupFiles: ['./tests/setup.ts'],
+ },
});
tsconfig.jsonã®è¨å®
Jestã®ãããªã°ãã¼ãã«APIã¨ãã¦ä½¿ç¨ããããã®è¨å®ããã¦ããã¾ãã
describeçã®importãçç¥ã§ããããã«ãªã£ã¦æ¥½ã§ãã
// tsconfig.json
{
...
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
+ "types": ["node", "vite/client", "vitest/globals"],
...
}
æå¾ã«package.jsonã®scriptsã¸ãã¹ãã®ã³ãã³ãã追å ãã¾ãã
// package.json
{
...
"scripts": {
...
+ "test": "vitest"
},
ãã¹ããæ¸ãã¦ã¿ã
æå¾ã«ããã¸ã§ã¯ãä½ææã«çæãããã³ã³ãã¼ãã³ãã«å¯¾ãã¦ç°¡åãªãã¹ããæ¸ãã¦ã¿ã¾ãã
react-routerãæä¾ããcreateRoutesStub
ã使ç¨ãããã¨ã§ã³ã³ãã¼ãã³ããã«ã¼ãã£ã³ã°ã«ä¾åããç°¡æçã«ã¢ãã¯ãã¦ãã¹ããå¯è½ã§ãã
reactrouter.com
import { createRoutesStub } from 'react-router';
import { render, screen } from '@testing-library/react';
import Home from '~/routes/home';
describe('React Router initial page', () => {
it('renders the home page and navigation links', async () => {
const Stub = createRoutesStub([{ path: '/', Component: Home }]);
render(Stub );
const logo = screen.getAllByRole('img', { name: /react router/i }).at(0);
expect(logo).toBeInTheDocument();
await waitFor(() => {
screen.findByText('React Router Docs');
screen.findByText('Join Discord');
});
});
});
ãããã«
React RouterãPrismaãVitestã«ãããã¹ãç°å¢ã®æ§ç¯ãè¡ãã¾ããããªã«ãããåèã«ãªãã¨å¬ããã§ãã
ããã¾ã§èªãã§ããã ããããã¨ããããã¾ããã