Typescript, Graphql이 포함된 FullStack Nextjs
88883 단어 reactprismawebdevjavascript
그렇다면 이 문장에서 우리는 어떤 기술을 사용할 것인가.
PostgreSQL
최종 결과를 보고 싶으면 바로 이것을 보세요repo
Nextjs 프로젝트 만들기
Nextjs 프로젝트를 만들려면 다음 명령을 실행하십시오
npx create-next-app full-stack-nextjs --use-npm -e with-typescript
npx
는 npm 등록표에서 위탁 관리하는 의존항을 설치하고 관리하기 위한 CLI 도구이다.create-next-app
은nextjs 프로젝트를 만들고 모든 의존 항목을 설치하는 도구입니다.full-stack-nextjs
는 우리 프로젝트의 이름입니다.아니면, 당신은 당신의 프로젝트에 당신이 좋아하는 이름을 지어줄 수 있습니다.--use-npm
기본 패키지 관리자 사용npm
-e
정확한 npm 패키지에 사용with-typescript
이 프로젝트는 typescript를 사용하여 미리 설정됩니다.기타 소프트웨어 패키지
cd
을(를) full-stack-nextjs
로 변환하고 다음 명령을 실행하여 다른 패키지를 설치합니다.npm install @nexus/schema nexus-prisma apollo-server-micro @apollo/react-hooks apollo-client apollo-cache-inmemory @apollo/react-ssr apollo-link-http apollo-link-schema ts-node graphql graphql-tag express @prisma/cli @prisma/client --save
열기tsconfig.json
모든 내용을 삭제하고 다음 코드를 붙여넣기{
"compilerOptions": {
/*
Note that the "module" setting will be overriden by nextjs automatically
(cf. https://github.com/zeit/next.js/discussions/10780).
If you need to change it, you should use the --compiler-options or provide a separate
tsconfig.json entirely.
*/
"module": "esnext",
"target": "ES2019",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
},
"exclude": [
"node_modules"
],
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx"
]
}
더 알고 싶으면checkout this repo폴더 구조는 다음과 같습니다.만약 다르다면, 걱정하지 마라. 왜냐하면 우리는 어쨌든 대부분의 파일을 삭제할 것이다.
components/
Layout.tsx --> remove this file
List.tsx --> remove this file
ListDetail.tsx --> remove this file
ListItem.tsx --> remove this file
interface/
index.tsx
pages/
api/
users/ --> remove this folder
index.tsx
users/ --> remove this folder
[id].tsx
index.tsx
about.tsx --> remove this file
index.tsx
utils/ --> remove this folder
파일 및 폴더 삭제 후 업데이트pages/index.tsx
const IndexPage = () => (
<>
<h1>Hello Next.js 👋</h1>
</>
);
export default IndexPage;
폴더 구조는 다음과 같습니다.현재 실행
npm run dev
및 이동 localhost브라우저에서 유사한 내용을 보셔야 합니다
우리의 응용 프로그램 전단은 이미 준비가 다 되었다.이제 백엔드를 만듭니다.
API 라우팅
Nextjs Api Routes는 Next를 사용하여 API를 구축하는 간단한 솔루션을 제공합니다.회사 명
폴더pages/api
의 모든 파일은 /api/*
에 매핑되며 page
대신 API 노드로 간주됩니다.서버 측 번들일 뿐 클라이언트 번들 크기는 증가하지 않습니다.
우리는 이미 pages/api
목록을 가지고 있다.우리의 백엔드에는 별도의 작업 환경이 필요하지 않다.
시작합시다prisma
프리스마
Prisma 은 소스 데이터베이스 도구 패키지입니다.
모든 패키지를 설치하지 않은 경우 다음 명령을 실행하여 Prisma 클라이언트를 설치하십시오.npm install @prisma/cli @prisma/client --save-dev
설치 후 다음 명령을 통해prisma를 초기화합니다npx prisma init
상기 명령prisma
을 실행하면 프로젝트의 루트 디렉터리에 디렉터리를 만듭니다. 이 디렉터리는 두 개의 init 파일을 포함합니다..evn
환경 변수에 사용됨 .gitignore
schema.prisma
우리prisma 모드에 사용.env
파일
DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"
이제 자신의 데이터베이스를 가리키기 위해 연결 URL을 조정해야 합니다postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=SCHEMA
Prisma 은 소스 데이터베이스 도구 패키지입니다.
모든 패키지를 설치하지 않은 경우 다음 명령을 실행하여 Prisma 클라이언트를 설치하십시오.
npm install @prisma/cli @prisma/client --save-dev
설치 후 다음 명령을 통해prisma를 초기화합니다npx prisma init
상기 명령prisma
을 실행하면 프로젝트의 루트 디렉터리에 디렉터리를 만듭니다. 이 디렉터리는 두 개의 init 파일을 포함합니다..evn
환경 변수에 사용됨 .gitignore
schema.prisma
우리prisma 모드에 사용.env
파일DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"
이제 자신의 데이터베이스를 가리키기 위해 연결 URL을 조정해야 합니다postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=SCHEMA
USER
: 데이터베이스 사용자 이름PASSWORD
: 데이터베이스 사용자의 암호 PORT
: 데이터베이스 서버에서 실행되는 포트(PostgreSQL의 경우 일반적5432
DATABASE
: database의 이름SCHEMA
: 데이터베이스이 예에서, 나는 로컬 데이터베이스를 사용할 것이다.
및
shcema.prisma
파일datasource db {
provider = "postgresql" //Database Alternatively you can use MySQL or SQLite
url = env("DATABASE_URL") // url from .env file
}
generator client {
provider = "prisma-client-js" // To Genetate prisma client
}
Prisma 모드 추가datasource db {
provider = "postgresql" //Database Alternatively you can use MySQL or SQLite
url = env("DATABASE_URL") // url from .env file
}
generator client {
provider = "prisma-client-js" // To Genetate prisma client
}
// Add Two Model User and Post
model User {
email String @unique
password String
id Int @default(autoincrement()) @id
name String?
posts Post[]
}
model Post {
authorId Int?
content String?
id Int @default(autoincrement()) @id
published Boolean @default(false)
title String
author User? @relation(fields: [authorId], references: [id])
}
스크립트를 추가하십시오package.json
"generate": "npm -s run generate:prisma && npm -s run generate:nexus",
"dev:migrate": "prisma2 migrate save --experimental -c && prisma2 migrate up --experimental -c",
"generate:prisma": "prisma generate",
"generate:nexus": "ts-node --transpile-only -P nexus.tsconfig.json pages/api"
schema 현재 실행
npm run dev:migrate
너는 이런 것을 보아야 한다* 유사한 컨텐츠를 보지 못하고 오류 메시지가 표시되면 데이터베이스 자격 증명이 제대로 추가되었는지 확인* 추가 정보를 찾을 수 있습니다
우리의 모형을 직관적으로 보다
실행
npx prisma studio
및 액세스http://localhost:5555/
프로젝트가 새 파일을 만드는 경우 루트 디렉토리nexus.tsconfig.json
{
/*
This file is used as a workaround for https://github.com/graphql-nexus/schema/issues/391
It allows the nexus schema generation to work (done via `npm run generate:nexus`).
*/
"compilerOptions": {
"sourceMap": true,
"outDir": "dist",
"strict": true,
"lib": ["esnext"],
"esModuleInterop": true
}
}
여기 있다 백엔드 서버
pages/api
에서 새 파일 만들기index.ts
서버를 만듭니다.서버를 만들려면 설치되지 않은 경우
apollo-server-micro
를 사용합니다npm install apollo-server-micro
.참고: 붙여넣기를 복사하는 경우 복사하지 마십시오.네가 복사해야 할 때 나는 쪽지를 남길 것이다
import { ApolloServer } from 'apollo-server-micro'
const server = new ApolloServer();
export default server.createHandler({
path: "/api",
});
하지만 우리 아폴로 서버는 패턴이 필요해.하나 만듭니다.같은 파일에 다음 코드 추가
import { makeSchema } from "@nexus/schema";
import path from "path";
const schema = makeSchema({
types: [], // we will create types later
outputs: {
typegen: path.join(process.cwd(), "pages", "api", "nexus-typegen.ts"),
schema: path.join(process.cwd(), "pages", "api", "schema.graphql"),
},
});
생성TypeDefs
prisma 가져오기@prisma/client
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
사용자 및 게시 모델 유형const User = objectType({
name: 'User',
definition(t) {
t.int('id')
t.string('name')
t.string('email')
t.list.field('posts', {
type: 'Post',
resolve: parent =>
prisma.user
.findOne({
where: { id: Number(parent.id) },
})
.posts(),
})
},
})
const Post = objectType({
name: 'Post',
definition(t) {
t.int('id')
t.string('title')
t.string('content', {
nullable: true,
})
t.boolean('published')
t.field('author', {
type: 'User',
nullable: true,
resolve: parent =>
prisma.post
.findOne({
where: { id: Number(parent.id) },
})
.author(),
})
},
})
* 변경 및 질의*
const Query = objectType({
name: 'Query',
definition(t) {
t.field('post', {
type: 'Post',
args: {
postId: stringArg({ nullable: false }),
},
resolve: (_, args) => {
return prisma.post.findOne({
where: { id: Number(args.postId) },
})
},
})
t.list.field('feed', {
type: 'Post',
resolve: (_parent, _args, ctx) => {
return prisma.post.findMany({
where: { published: true },
})
},
})
t.list.field('drafts', {
type: 'Post',
resolve: (_parent, _args, ctx) => {
return prisma.post.findMany({
where: { published: false },
})
},
})
t.list.field('filterPosts', {
type: 'Post',
args: {
searchString: stringArg({ nullable: true }),
},
resolve: (_, { searchString }, ctx) => {
return prisma.post.findMany({
where: {
OR: [
{ title: { contains: searchString } },
{ content: { contains: searchString } },
],
},
})
},
})
},
})
const Mutation = objectType({
name: "Mutation",
definition(t) {
t.field("signupUser", {
type: "User",
args: {
name: stringArg(),
email: stringArg({ nullable: false }),
password: stringArg({ nullable: false }),
},
resolve: (_, { name, email, password }, ctx) => {
return prisma.user.create({
data: {
name,
email,
password,
},
});
},
});
t.field("deletePost", {
type: "Post",
nullable: true,
args: {
postId: stringArg(),
},
resolve: (_, { postId }, ctx) => {
return prisma.post.delete({
where: { id: Number(postId) },
});
},
});
t.field("createDraft", {
type: "Post",
args: {
title: stringArg({ nullable: false }),
content: stringArg(),
authorEmail: stringArg(),
},
resolve: (_, { title, content, authorEmail }, ctx) => {
return prisma.post.create({
data: {
title,
content,
published: false,
author: {
connect: { email: authorEmail },
},
},
});
},
});
t.field("publish", {
type: "Post",
nullable: true,
args: {
postId: stringArg(),
},
resolve: (_, { postId }, ctx) => {
return prisma.post.update({
where: { id: Number(postId) },
data: { published: true },
});
},
});
},
});
유형을 우리 모드로 전달
const schema = makeSchema({
types: [Query, Mutation, Post, User],
outputs: {
typegen: path.join(process.cwd(), "pages", "api", "nexus-typegen.ts"),
schema: path.join(process.cwd(), "pages", "api", "schema.graphql"),
},
});
지금 너의 서류는 이렇게 될 것이다참고: 이 코드를 복사하여 서버에 붙여넣을 수 있습니다.ts 파일
import { makeSchema, objectType, stringArg } from "@nexus/schema";
import { PrismaClient } from "@prisma/client";
import { ApolloServer } from "apollo-server-micro";
import path from "path";
const prisma = new PrismaClient();
const User = objectType({
name: "User",
definition(t) {
t.int("id");
t.string("name");
t.string("email");
t.list.field("posts", {
type: "Post",
resolve: (parent) =>
prisma.user
.findOne({
where: { id: Number(parent.id) },
})
.posts(),
});
},
});
const Post = objectType({
name: "Post",
definition(t) {
t.int("id");
t.string("title");
t.string("content", {
nullable: true,
});
t.boolean("published");
t.field("author", {
type: "User",
nullable: true,
resolve: (parent) =>
prisma.post
.findOne({
where: { id: Number(parent.id) },
})
.author(),
});
},
});
const Query = objectType({
name: "Query",
definition(t) {
t.field("post", {
type: "Post",
args: {
postId: stringArg({ nullable: false }),
},
resolve: (_, args) => {
return prisma.post.findOne({
where: { id: Number(args.postId) },
});
},
});
t.list.field("feed", {
type: "Post",
resolve: (_parent, _args, ctx) => {
return prisma.post.findMany({
where: { published: true },
});
},
});
t.list.field("drafts", {
type: "Post",
resolve: (_parent, _args, ctx) => {
return prisma.post.findMany({
where: { published: false },
});
},
});
t.list.field("filterPosts", {
type: "Post",
args: {
searchString: stringArg({ nullable: true }),
},
resolve: (_, { searchString }, ctx) => {
return prisma.post.findMany({
where: {
OR: [
{ title: { contains: searchString } },
{ content: { contains: searchString } },
],
},
});
},
});
},
});
const Mutation = objectType({
name: "Mutation",
definition(t) {
t.field("signupUser", {
type: "User",
args: {
name: stringArg(),
email: stringArg({ nullable: false }),
password: stringArg({ nullable: false }),
},
resolve: (_, { name, email, password }, ctx) => {
return prisma.user.create({
data: {
name,
email,
password,
},
});
},
});
t.field("deletePost", {
type: "Post",
nullable: true,
args: {
postId: stringArg(),
},
resolve: (_, { postId }, ctx) => {
return prisma.post.delete({
where: { id: Number(postId) },
});
},
});
t.field("createDraft", {
type: "Post",
args: {
title: stringArg({ nullable: false }),
content: stringArg(),
authorEmail: stringArg(),
},
resolve: (_, { title, content, authorEmail }, ctx) => {
return prisma.post.create({
data: {
title,
content,
published: false,
author: {
connect: { email: authorEmail },
},
},
});
},
});
t.field("publish", {
type: "Post",
nullable: true,
args: {
postId: stringArg(),
},
resolve: (_, { postId }, ctx) => {
return prisma.post.update({
where: { id: Number(postId) },
data: { published: true },
});
},
});
},
});
export const schema = makeSchema({
types: [Query, Mutation, Post, User],
outputs: {
typegen: path.join(process.cwd(), "pages", "api", "nexus-typegen.ts"),
schema: path.join(process.cwd(), "pages", "api", "schema.graphql"),
},
});
export const config = {
api: {
bodyParser: false,
},
};
export default new ApolloServer({ schema }).createHandler({
path: "/api",
});
Apollo 클라이언트를 통해 백엔드를 프런트엔드에 연결
우리 프로젝트의 루트 디렉터리에 새 파일 apollo/clinet.js
을 만들고 다음 코드를 붙여넣습니다.
참고: 이러한 패키지가 필요합니다@apollo/react-hooks apollo-client apollo-cache-inmemory @apollo/react-ssr apollo-link-http apollo-link-schema
import React from 'react'
import Head from 'next/head'
import { ApolloProvider } from '@apollo/react-hooks'
import { ApolloClient } from 'apollo-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
let apolloClient = null
/**
* Creates and provides the apolloContext
* to a next.js PageTree. Use it by wrapping
* your PageComponent via HOC pattern.
* @param {Function|Class} PageComponent
* @param {Object} [config]
* @param {Boolean} [config.ssr=true]
*/
export function withApollo(PageComponent, { ssr = true } = {}) {
const WithApollo = ({ apolloClient, apolloState, ...pageProps }) => {
const client = apolloClient || initApolloClient(apolloState)
return (
<ApolloProvider client={client}>
<PageComponent {...pageProps} />
</ApolloProvider>
)
}
// Set the correct displayName in development
if (process.env.NODE_ENV !== 'production') {
const displayName =
PageComponent.displayName || PageComponent.name || 'Component'
if (displayName === 'App') {
console.warn('This withApollo HOC only works with PageComponents.')
}
WithApollo.displayName = `withApollo(${displayName})`
}
if (ssr || PageComponent.getInitialProps) {
WithApollo.getInitialProps = async ctx => {
const { AppTree } = ctx
// Initialize ApolloClient, add it to the ctx object so
// we can use it in `PageComponent.getInitialProp`.
const apolloClient = (ctx.apolloClient = initApolloClient())
// Run wrapped getInitialProps methods
let pageProps = {}
if (PageComponent.getInitialProps) {
pageProps = await PageComponent.getInitialProps(ctx)
}
// Only on the server:
if (typeof window === 'undefined') {
// When redirecting, the response is finished.
// No point in continuing to render
if (ctx.res && ctx.res.finished) {
return pageProps
}
// Only if ssr is enabled
if (ssr) {
try {
// Run all GraphQL queries
const { getDataFromTree } = await import('@apollo/react-ssr')
await getDataFromTree(
<AppTree
pageProps={{
...pageProps,
apolloClient,
}}
/>
)
} catch (error) {
// Prevent Apollo Client GraphQL errors from crashing SSR.
// Handle them in components via the data.error prop:
// https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error
console.error('Error while running `getDataFromTree`', error)
}
// getDataFromTree does not call componentWillUnmount
// head side effect therefore need to be cleared manually
Head.rewind()
}
}
// Extract query data from the Apollo store
const apolloState = apolloClient.cache.extract()
return {
...pageProps,
apolloState,
}
}
}
return WithApollo
}
/**
* Always creates a new apollo client on the server
* Creates or reuses apollo client in the browser.
* @param {Object} initialState
*/
function initApolloClient(initialState) {
// Make sure to create a new client for every server-side request so that data
// isn't shared between connections (which would be bad)
if (typeof window === 'undefined') {
return createApolloClient(initialState)
}
// Reuse client on the client-side
if (!apolloClient) {
apolloClient = createApolloClient(initialState)
}
return apolloClient
}
/**
* Creates and configures the ApolloClient
* @param {Object} [initialState={}]
*/
function createApolloClient(initialState = {}) {
const ssrMode = typeof window === 'undefined'
const cache = new InMemoryCache().restore(initialState)
return new ApolloClient({
ssrMode,
link: createIsomorphLink(),
cache,
})
}
function createIsomorphLink() {
const { HttpLink } = require('apollo-link-http')
return new HttpLink({
uri: 'http://localhost:3000/api',
credentials: 'same-origin',
})
}
이제 pages/index.ts
로 이동하여 가져오기WithApollo
import { withApollo } from "../apollo/client";
const IndexPage = () => (
<>
<h1>Hello Next.js 👋</h1>
</>
);
export default withApollo(IndexPage);
우리 package.json
에는 generate
라는 스크립트가 있습니다."generate": "npm -s run generate:prisma && npm -s run generate:nexus",
이 명령은 생성 유형과 패턴을 책임집니다.
이 명령을 실행하면 pages/api
nexus-typegen.ts
및 schema.graphql
에서 두 개의 파일을 볼 수 있습니다
이제 계속합시다http://localhost:3000/api
여기 있습니다.너는 창고 응용 프로그램을 구축하기 위해 이 프로젝트를 계속할 수 있다.
다음 글에서, 이 흐름을 사용하여 신분 검증을 할 수 있음을 보여 드리겠습니다.
Reference
이 문제에 관하여(Typescript, Graphql이 포함된 FullStack Nextjs), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://dev.to/imranib/fullstack-nextjs-with-typescript-graphql-2ce0
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
import React from 'react'
import Head from 'next/head'
import { ApolloProvider } from '@apollo/react-hooks'
import { ApolloClient } from 'apollo-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
let apolloClient = null
/**
* Creates and provides the apolloContext
* to a next.js PageTree. Use it by wrapping
* your PageComponent via HOC pattern.
* @param {Function|Class} PageComponent
* @param {Object} [config]
* @param {Boolean} [config.ssr=true]
*/
export function withApollo(PageComponent, { ssr = true } = {}) {
const WithApollo = ({ apolloClient, apolloState, ...pageProps }) => {
const client = apolloClient || initApolloClient(apolloState)
return (
<ApolloProvider client={client}>
<PageComponent {...pageProps} />
</ApolloProvider>
)
}
// Set the correct displayName in development
if (process.env.NODE_ENV !== 'production') {
const displayName =
PageComponent.displayName || PageComponent.name || 'Component'
if (displayName === 'App') {
console.warn('This withApollo HOC only works with PageComponents.')
}
WithApollo.displayName = `withApollo(${displayName})`
}
if (ssr || PageComponent.getInitialProps) {
WithApollo.getInitialProps = async ctx => {
const { AppTree } = ctx
// Initialize ApolloClient, add it to the ctx object so
// we can use it in `PageComponent.getInitialProp`.
const apolloClient = (ctx.apolloClient = initApolloClient())
// Run wrapped getInitialProps methods
let pageProps = {}
if (PageComponent.getInitialProps) {
pageProps = await PageComponent.getInitialProps(ctx)
}
// Only on the server:
if (typeof window === 'undefined') {
// When redirecting, the response is finished.
// No point in continuing to render
if (ctx.res && ctx.res.finished) {
return pageProps
}
// Only if ssr is enabled
if (ssr) {
try {
// Run all GraphQL queries
const { getDataFromTree } = await import('@apollo/react-ssr')
await getDataFromTree(
<AppTree
pageProps={{
...pageProps,
apolloClient,
}}
/>
)
} catch (error) {
// Prevent Apollo Client GraphQL errors from crashing SSR.
// Handle them in components via the data.error prop:
// https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error
console.error('Error while running `getDataFromTree`', error)
}
// getDataFromTree does not call componentWillUnmount
// head side effect therefore need to be cleared manually
Head.rewind()
}
}
// Extract query data from the Apollo store
const apolloState = apolloClient.cache.extract()
return {
...pageProps,
apolloState,
}
}
}
return WithApollo
}
/**
* Always creates a new apollo client on the server
* Creates or reuses apollo client in the browser.
* @param {Object} initialState
*/
function initApolloClient(initialState) {
// Make sure to create a new client for every server-side request so that data
// isn't shared between connections (which would be bad)
if (typeof window === 'undefined') {
return createApolloClient(initialState)
}
// Reuse client on the client-side
if (!apolloClient) {
apolloClient = createApolloClient(initialState)
}
return apolloClient
}
/**
* Creates and configures the ApolloClient
* @param {Object} [initialState={}]
*/
function createApolloClient(initialState = {}) {
const ssrMode = typeof window === 'undefined'
const cache = new InMemoryCache().restore(initialState)
return new ApolloClient({
ssrMode,
link: createIsomorphLink(),
cache,
})
}
function createIsomorphLink() {
const { HttpLink } = require('apollo-link-http')
return new HttpLink({
uri: 'http://localhost:3000/api',
credentials: 'same-origin',
})
}
import { withApollo } from "../apollo/client";
const IndexPage = () => (
<>
<h1>Hello Next.js 👋</h1>
</>
);
export default withApollo(IndexPage);
Reference
이 문제에 관하여(Typescript, Graphql이 포함된 FullStack Nextjs), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/imranib/fullstack-nextjs-with-typescript-graphql-2ce0텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)