nexus安装_使用 Next.js , Nexus, Prisma 构建全栈项目

06975491da41f8a0fc7d1ddea008fe1c.png

使用 Next.js , Nexus, Prisma 构建全栈项目

此教程翻译自《Complete Intruduction to Fullstack,Type-Safe GraphQL(feat.Next.js,Nexus,Prisma)》

我们将要从模板中构建全栈项目,会使用 Next.js ,Nexus,Prisma ,来实现 React 的后端渲染,GraphQL 接口。

技术栈浅析

首先,我们浅析一下我们选择的技术栈。

  • TypeScript - 前后端使用统一的编程语言
  • React 和 Next.js - 前端开发框架 React ,以及React 的服务端渲染库
  • Urql GraphQL client - GraphQL 的客户端
  • PostgreSQL - 数据库
  • Nexus - 一个 code-first 的 GraphQL 服务端
  • Prisma Client 和 Prisma Migrate - 用于数据库 ORM 操作的工具库(注意:Prisma Migrate 仍然在实验阶段)

现在,我们开始吧!

配置开发环境

在我们开始编写代码前,首先要配置开发环境所需要的软件,以便我们能够使用轻松的方式编写代码。
我们使用的软件编辑器是 VS Code , 它的插件库里提供了 Prisma 和 GraphQL 的代码高亮和自动格式化工具,我们先在 VS Code 中安装下面两个插件。

972e91abdbc050ad3c5b073e67d07662.png

ed5d503f3a382ed5501e032cfc635dcd.png


接下来,我们要安装 docker (我们的 PostgreSQL 数据库将在 docker 容器中运行,当然其他的云数据库或本地安装的数据库亦可),具体安装教程请前往 docker 官网。

注意事项

我们的开发工具是 mac pro 笔记本电脑,在 window 系统下的 pc 电脑运行结果如有不同,请先自行查找原因,然后在评论区留言讨论。 在命令行下运行指令,如无特别说明,一律是在项目根目录下运行。

新建一个 Next.js 项目

我们可以使用 ceate-next-app 工具包新建一个 Next.js 项目。在命令行工具输入下面的指令:

npx create-next-app prisma-next-nexus --use-yarn -e with-typescript

create-next-app 会使用 Git 自动下载项目初始化代码,并自动安装所有的依赖。指令运行完成以后,在 VS Code 打开项目目录,可以看到下图所示的项目结构。

4370b23945cbfa3ac4bb8da489e43609.png

安装 Nexus 和 Prisma

现在,我们安装 Nexus 框架和 nexus-plugin-prisma 包。在命令行工具中运行下面的指令:

yarn add nexus @prisma/client && yarn add  @prisma/cli -D

Nexus 包含了提供 Typescript 语言类型编译支持的插件 Nexus TypeScript Language Service Plugin ,我们只需要在 Typescript 配置文件 tsconfig.json 中配置即可,修改代码如下:

{
  "compilerOptions": {
    // ...
    "noEmit": true,
    "rootDir": ".",
    "typeRoots": ["node_modules/@types", "types"],
    "plugins": [{ "name": "nexus/typescript-language-service" }] // 新增这一行
  },
  // ...
  "include": ["**/*.ts", "**/*.tsx", ".", "types.d.ts"]
}

使用 Docker 启动数据库

我们使用 docker-compose 保存 postgresql 容器的设置,在项目根目录下添加 docker-compose-yml 文件,文件内容如下:

version: '3.1'

services:
  db:
    image: postgres:11.7
    container_name: prisma-next-nexus-postgre
    restart: always
    environment:
      POSTGRES_USER: prismaNextNexus
      POSTGRES_PASSWORD: ${POSTGRESPWD}
    ports:
      - 54333:5432
    volumes:
      - ./db/postgresql:/var/lib/postgresql/data

其中 ${POSTGRESPWD} 是保存在 .env 文件中的数据库密码变量。同样的,在根目录下新建 .env 文件,并添加下面的代码:

POSTGRESPWD=xxxxxx  // 密码

然后在命令行中输入如下指令:

docker-compose up -d

启动过程没有报错,即成功,命令会自动退出。 -d 时表示docker 容器在后台运行。

Prisma 连接数据库

我们要使用 Prisma 连接到上一步启动的 postgresql 数据库容器。首先,我们在根目录下新建 prisma 文件夹,然后在其中添加 .env 文件,然后在 .env 文件中添加环境变量 DATABASE_URL ,代码如下“

DATABASE_URL="postgresql://prismaNextNexus:xxx@localhost:54333/postgres"
别忘了替换数据库的密码

因为 .env 文件里包含了密码这样的敏感信息,不应该提交到 git 仓库中,所以在 .gitignore 中添加省略 .env
最后,我们需要在 /prisma 创建 schema 文件,用于设置数据库配置,和数据库 model 描述。具体代码如下:

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model Hello {
  id        Int      @id @default(autoincrement())
  createdAt DateTime @default(now())
}

使用 Prisma 初始化数据库表结构

schema.prisma 写入的 model 描述就是数据库表的描述。我们接下来就要使用 @prisma/cli 工具在 数据库中自动创建,更新数据表。
在命令行工具中输入下面的指令:

yarn prisma migrate save --name init --experimental

运行结果如下图:

e1d610ecb018f54e0d3a64a933802a1d.png


此时,Prisma 已经在 prisma 目录下创建了连接文件 migration

721423680bad7640df9b8fab282de523.png


最后,将连接的信息推送到数据中使用下面的指令:

yarn run prisma migrate up --experimental

运行完成以后,可以在数据库中看到我们在 prisma.schema 定义的 Hello

b55b4a6705745ca6ff726461a7b1945e.png
我们使用的数据库 UI 工具是 TablePlus。

连接 Nexus 和 Next.js

Next.js 的 API routes 功能非常方便,我们只需要添加 /pages/api/graphql.ts 文件,然后启动 next.js 服务,就可以在 http://app-domain/api/graphql 访问 GraphqQL 服务。
graphql.ts 文件中编写下面的代码:

import app, { server } from "nexus";
import "../../graphql/schema"; // we'll create this file in a second!

app.assemble();

export default server.handlers.graphql;

下面,我们在根目录创建 graphql 目录,并且在这个目录下新建 schema.ts 文件。在 schema.ts 文件中,我们首先初始化 nexus-plugin-prisma 插件,设置 CRUD 功能,并通过 plugin 和 nexus 连接。具体代码如下:

import { use } from "nexus";
import { prisma } from "nexus-plugin-prisma";

use(prisma({ features: { crud: true } }));

最后,在命令行中启动 nexus 服务

yarn run nexus dev

78dcfe55735090b086c4f18be7f2881a.png

此时,graphql 已经启动,访问 localhost:4000/graphql 可以看到 GraphQL Playground 已经运行(目前只有空的 schema)。

f0c534567dd1cb020cd74146fe7ddffa.png

编写 GrahphQL API

现在,我们开始写一个 API。

定义对象类型

首先,我们在 graphql/shema.ts 中定义一个 User 对象类型,这里使用的 nexus 定义对象的语法:

schema.objectType({
  name: "User",
  definition(t) {
    t.model.id();
    t.model.name();
  },
})

当你在 VS Code 中编写上面代码时,VS Code 将会自动完成这些字段( id , name )。这是因为我们已经在 prisma/schema.prisma 中定义好了 User model。
现在,打开 Grapqhl Playground 并切换到 Schema 标签页。你会看到已经添加了 Grqphql 对象类型 User

025294e88cbfdc22450eec10053c04fd.png

定义 Query 类型

Nexus 使用 schema.queryType 函数来定义 root Query
我们写一个查询全部 Userquery - allUsers
具体代码如下:

schema.queryType({
  definition(t) {
    t.list.field("allUsers", {
      type: "User",
      resolve(_parent, _args, ctx) {
        return ctx.db.user.findMany();
      },
    });
  },
});

resolve 函数中,可以添加实际的逻辑代码。Prisma 客户端里可以操作 数据的实例 db 包含在上下文对象 ctx 中。 更多关于 Prisma 客户端 API 的信息可以在官网文档中了解。
Nexus-Prisma plugin 也包含了从数据库读取数据的方法,下面的代码可以直接定义读取 user users 的 query。

schema.queryType({
  definition(t) {
    t.list.field("allUsers", {
      type: "User",
      resolve(_parent, _args, ctx) {
        return ctx.db.user.findMany();
      },
    });
    t.crud.user(); // nexus-prisma 定义的
    t.crud.users(); // nexus-prisma 定义的
  },
});

别忘了,查询单个 user 时传入参数的 Input 类型描述也会自动生成。

input UserWhereUniqueInput {
    id: String
}

定义 Mutation 类型

举一反三,和 Query type 类似, Mutation 类型使用 schema.mutationType 类定义。
接下来,让我们来创建一个 bigRedButton mutation 用来删除所有的 user 数据。代码如下:

schema.mutationType({
  definition(t) {
    t.field("bigRedButton", {
      type: "String",
      async resolve(_parent, _args, ctx) {
        const { count } = await ctx.db.user.deleteMany({});
        return `${count} user(s) destroyed.`;
      },
    });
  },
});

同样的,nexus-prisma plutin 也为 mutation 提供了很多 CURD 函数。

schema.mutationType({
  definition(t) {
    t.field("bigRedButton", {
      type: "String",
      async resolve(_parent, _args, ctx) {
        const { count } = await ctx.db.user.deleteMany({});
        return `${count} user(s) destroyed.`;
      },
    });
    t.crud.createOneUser(); // 创建一个用户
    t.crud.deleteOneUser(); // 删除一个用户
    t.crud.updateOneUser(); // 更新用户信息
    t.crud.updateManyUser(); // 更新多个用户信息
  },
});

自动生成的 GraphQL schema 如下:

type Mutation {
  bigRedButton: String
  createOneUser(data: UserCreateInput!): User!
  deleteOneUser(where: UserWhereUniqueInput!): User
  updateOneUser(data: UserUpdateInput!, where: UserWhereUniqueInput!): User
  updateManyUser(
    data: UserUpdateManyMutationInput!
    where: UserWhereInput
  ): BatchPayload!
}


现在,我们完整的 GraphqlQL API 已经完成了!接下来,我们尝试在 React 中使用 GraphQL 发送请求进行增删改查。

在 React 中使用 GraphQL

我们将会在前端代码中使用 Urql 。当然,也可以使用其他你熟悉的 GrphQL 客户端,比如 Apollo client 等。

设置 Urql GraphQL 客户端

首先,在命令行工具运行下面的安装指令:

yarn add graphql-tag next-urql react-is urql isomorphic-unfetch

然后,在 /pages 目录下新增 _app.tsx 文件。这个文件是特殊的 Next.js 组件,它会在初始化每一个页面时都会被执行。
我们在 /pages/_app.tsx 文件中添加下面的代码:

import React from "react";
import { withUrqlClient, NextUrqlAppContext } from "next-urql";
import NextApp, { AppProps } from "next/app";
import fetch from "isomorphic-unfetch";

// the URL to /api/graphql
const GRAPHQL_ENDPOINT = `http://localhost:3000/api/graphql`;

const App = ({ Component, pageProps }: AppProps) => {
  return <Component {...pageProps} />;
};

App.getInitialProps = async (ctx: NextUrqlAppContext) => {
  const appProps = await NextApp.getInitialProps(ctx);
  return { ...appProps };
};

export default withUrqlClient((_ssrExchange, _ctx) => ({
  url: GRAPHQL_ENDPOINT,
  fetch,
}))(
  // @ts-ignore
  App
);

现在,在前端所有页面都可以使用 GrphQL 客户端来发送网络请求了!

使用 GraphQL client 查询所有用户

首先,在根目录下创建 components 文件夹,用来保存所有的组件文件。在 components 文件夹下创建 AllUsers.tsxAllUsers.tsx 文件里定义 React 函数组件,组件内部将会请求 allUsers GraphQL query 并把查询返回的结果显示出来。
我们现在 AllUser.tsx 中定义 gql 查询语句,如下代码:

import gql from "graphql-tag";

const AllUsersQuery = gql`
  query AllUsers {
    allUsers {
      id
      name
    }
  }
`;

接下来,定义查询请求返回后的数据类型,我们知道返回数据是一个包好所有 User 对象的数组。所以,我们定义如下类型:

type AllUsersData = {
  allUsers: {
    id: string;
    name: string;
  }[];
}

最后,我们完成 React 函数组件,并发送请求获取所有的 User 数据。

import React from "react";
import {useQuery} from 'urql';
...

export default function AllUsers() {
  const [result] = useQuery<AllUsersData>({ // 发送请求,更多 API 信息请查看 urql 官网
    query: AllUsersQuery,
  });
  const { data, fetching, error } = result;

  if (fetching) return <p>Loading...</p>;
  if (error) return <p>Oh no... {error.message}</p>;

  return (
    <div>
      <p>There are {data?.allUsers?.length} user(s) in the database:</p>
      <ul>
        {data?.allUsers?.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

完整版的函数组件代码如下:

import React from "react";
import {useQuery} from 'urql';
import gql from "graphql-tag";

const AllUsersQuery = gql`
  query AllUsers {
    allUsers {
      id
      name
    }
  }
`;

type AllUsersData = {
  allUsers: {
    id: string;
    name: string;
  }[];
}

export default function AllUsers() {
  const [result] = useQuery<AllUsersData>({ // 发送请求,更多 API 信息请查看 urql 官网
    query: AllUsersQuery,
  });
  const { data, fetching, error } = result;

  if (fetching) return <p>Loading...</p>;
  if (error) return <p>Oh no... {error.message}</p>;

  return (
    <div>
      <p>There are {data?.allUsers?.length} user(s) in the database:</p>
      <ul>
        {data?.allUsers?.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

最后,我们在 /pages/index.tsx 主页文件中渲染 AllUsers 组件,代码如下:

import AllUsers from "../components/AllUsers";

const IndexPage = () => (
  <div>
    <h1>Hello Next.js  </h1>
    {/* === Tada! === */}
    <AllUsers />
  </div>
);

export default IndexPage;

接下来,我们在命令行启动 next.js App ~

yarn dev

最后,访问 localhost:3000

14fdc42019e1fd5c1567c122bdb42bd0.png

自动生成 useQuery hooks 和 types

如果我们想要自动生成 GraphQL API 的类型描述而不是手动定义所有的,我们可以使用一个非常酷的工具 GraphQL Code Generator ,它可以从 Nexus GraphQL 入口 schema 描述中直接生成客户端所需的类型定义代码。这样,我们就只需在 schema.prisma 文件定义一次类型,然后,客户端所使用的类型都会自动通过 GraphQL Code Generator 自动生成。
首先,在命令行工具运行下面的代码来安装所需的包:

yarn add -D @graphql-codegen/cli @graphql-codegen/typescript 
@graphql-codegen/typescript-operations 
@graphql-codegen/typescript-urql

然后,我们在根目录下添加 codegen.yml 配置文件来告诉 GraphQL Code Generator 的 GraphQL schema 访问地址等信息,我们项目的配置信息如下:

overwrite: true
schema: "http://localhost:4000/graphql" # GraphQL endpoint via the nexus dev server
documents: "graphql/**/*.graphql.ts" # parse graphql operations in matching files
generates:
  generated/graphql.tsx: # location for generated types, hooks and components
    plugins:
      - "typescript"
      - "typescript-operations"
      - "typescript-urql"
    config:
      withComponent: false # we'll use Urql client with hooks instead
      withHooks: true

然后,我们需要在 graphql 目录下新建 queries.graphql.ts 文件,并将 components/AllUsers.tsx 中的 gql 标签对象剪切到 queries.graphql.ts 中。具体代码如下图:

import gql from "graphql-tag";

export const AllUsersQuery = gql`
  query AllUsers {
    allUsers {
      id
      name
    }
  }
`;

最后,别忘了启动 nexus ,之后我们就可以在命令行运行如下指令,来自动生成类型定义了:

yarn graphql-codegen

2051b493e06216bf7f49c89e9ec27705.png


如果希望开发过程中监听文件改变,自动生成类型定义的话,可以添加 --watch 参数。
指令运行成功以后,我们可以在产出目录 generated 中查看 graphql.tsx 文件内容,部分代码如下:

...
export function useAllUsersQuery(options: Omit<Urql.UseQueryArgs<AllUsersQueryVariables>, 'query'> = {}) {
  return Urql.useQuery<AllUsersQuery>({ query: AllUsersDocument, ...options });
};
...

最后,我们在 components/AllUsers.tsx 中使用生成 useAllUsersQuery 函数来发送请求获取用户数据。代码如下:

import React, { useEffect } from "react";
import {
  useAllUsersQuery,
} from "../generated/graphql";

export default function AllUsers() {
  const [result] = useAllUsersQuery(); // 直接调用函数即可
  const { data, fetching, error } = result;

  if (fetching) return <p>Loading...</p>;
  if (error) return <p>Oh no... {error.message}</p>;

  return (
    <div>
      <p>There are {data?.allUsers?.length} user(s) in the database:</p>
      <ul>
        {data?.allUsers?.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

结尾

我们希望你能喜欢这篇实践教程,并学习到如何配置 prisma,nexus,nextjs 来完成全栈的开发!
你可以访问此 Github repo 下的 prisma-next-nexus 目录来查看完整代码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值