Hasura 101:使用 Graphql、Postgres 和 React 构建实时游戏
2019 年我最喜欢的技术发现是Hasura。它让完整的 Postgres + GraphQL 后端的启动和运行变得轻而易举——你几乎只需点击几下,就能拥有一个完全交互式的数据库浏览器和编辑器,以及可以用 GraphIQL 测试的 GraphQL 端点。我想分享一个关于在 Hasura 上构建实时游戏(使用 websockets!!!)的教程,该教程基于我今年早些时候和 Hasura 一起参加的一个研讨会。
我们将讨论什么是 GraphQL,什么是 Hasura,如何设置 Hasura 和数据库,然后在其上构建一个完整的 React 应用。我们将构建一个如下所示的绘图应用程序:
这是它的部署版本!(注意:它使用的是 Heroku 免费套餐,因此可能需要几秒钟才能启动)
什么是 GraphQl
根据其文档,“GraphQL 是一种 API 查询语言”。传统上,使用 REST API,您可以通过各种端点访问不同的数据或以某种方式更改数据。这很快就会变得非常庞大,如果您的前端和后端团队各自独立工作,甚至可能成为瓶颈。随着我们的应用程序不断发展并需要显示不同的数据,GraphQL 变得非常有用。
Sacha Grief 在他们的文章“那么我一直听到的这个 GraphQL 是什么?”中写了一个很棒的比喻。
旧的 REST 模型就像订披萨,然后叫杂货送货上门,最后打电话给干洗店取衣服。三个商店,打三个电话。
另一方面,GraphQL 就像拥有一个私人助理:一旦你向他们提供了这三个地方的地址,你就可以简单地询问你想要的东西(“给我拿件干洗衣物、一个大披萨和两打鸡蛋”)并等待他们回来。
GraphQL 也与语言无关(即您可以将 GraphQL 与任何编程语言一起使用),并且它位于您的客户端和数据源之间,因此它非常灵活!
Hasura 是什么
Hasura 允许您以闪电般的速度构建 GraphQL 后端——您只需单击按钮即可制作一些非常棒的东西。
哈苏拉:
- 为您提供新数据库或现有数据库上的即时实时 GraphQL API。
- 它配有仪表板,可帮助您设置 API 和数据库。
- 您可以根据需要使用 Web 钩子、外部 API 或无服务器功能对数据库中的变化做出反应。
- 您还可以将自定义 GraphQL API 和其他数据源拼接成统一的 GraphQL API。
使用 Hasura 启动并运行
- 转到此网址
- 登录 Heroku(如果还没有,请创建一个帐户,不用担心,它是免费的!)
- 为您的应用程序选择一个(唯一)名称
- 点击
Deploy app
- ✨神奇✨!您已部署并运行 Hasura 实例!
设置数据库
我们的应用程序使用PostgreSQL数据库,但 Hasura 为我们提供了一个与该数据库交互的非常好的界面。
转到您的 Hasura 应用程序,它应该看起来像这样:
单击data
选项卡,然后单击标题create table
旁边的按钮Schema
。
我们将pixels
在数据库中创建一个表来存储每个像素的坐标和颜色。
我们还将在该表中创建两列:id
,这将是 Postgres 为我们处理的自动递增整数,color
它将存储每个像素应采用的颜色。
另外,设置id
为主键。
您的表单应该是这样的!
然后,向下滚动到底部并单击add table
按钮!
我们现在有一个包含所需列的数据库🎉!
添加初始数据
我们的应用程序第一次加载时,我们希望每个像素只是一个白色框,直到人们开始为它们着色。为了使我们未来的代码更容易,我们将在数据库中植入 400 个具有颜色的值white
,因为网格是 20x20 的网格。
注意:您还可以为每个像素存储行、列和颜色,但我发现只需存储所有 400 个像素并在用户界面上按 ID 对它们进行排序就更容易了。
在 Hasura 仪表板中,我们可以通过单击SQL
表列表下的链接来运行 SQL 查询。
您可以将 SQL 添加到弹出的文本框中,然后按运行!
这是我用来填充初始数据库的查询。您可以复制粘贴相同的查询,以向数据库添加 400 个白色像素!
GraphQL 查询
现在我们已经将数据加载到数据库中,我们可以使用 GraphQL 来查询这些数据。我们不需要再做任何设置!您可以转到GraphIQL
Hasura 仪表板的选项卡并使用您的数据。
GraphIQL 是一个用于探索 GraphQL 查询的浏览器内置 IDE。它可以帮助你编写查询来获取和操作数据。
GraphQL 查询的语法与您可能习惯的 SQL 查询非常不同——它们看起来更类似于 JavaScript 对象。
例如,要获取所有数据,我们的查询将如下所示:
query GetPixels {
pixels {
id
color
}
}
首先,我们将查询命名为GetPixels
。然后,我们指定要从表中获取数据。我们还指定了要获取和列的pixels
数据。如果省略其中一列,则只会获取该列的数据。id
color
我们还可以更改查询,以便它始终按照像素的 ID 对像素进行排序:
query GetPixels {
pixels(order_by: { id: asc }) {
id
color
}
}
我们还可以编写subscriptions
通过 websockets 订阅数据变化的查询。
将上述示例中的单词更改query
为subscription
将允许我们在更新时提取新数据。
此外,GraphQL 还mutations
允许我们更新数据。
例如,以下查询将允许我们根据像素的 ID 更新其颜色:
mutation changePixelColor($id: Int!, $color: String!) {
update_pixels(where: { id: { _eq: $id } }, _set: { color: $color }) {
returning {
color
id
}
}
}
这个突变名为changePixelColor
,就像编程函数一样,突变(和查询)可以接受参数。在本例中,它接受id
,它是一个整数,以及 ,color
它是一个字符串。我们需要指定要查询的表,在本例中是pixels
,我们可以通过 来实现update_pixels
。然后,我们添加一个where
子句——我们只更新数据库中与id
指定项匹配的项目。然后我们指定_set
,其中我们表示将行的颜色设置为指定的颜色。
然后,我们添加一个returning
数据,其中包含我们想要在查询执行完成后发送回应用程序的数据。
我强烈建议在 GraphIQL 中测试这些查询并使用它来构建自定义查询——它会为您做很多事情!
如果您想深入了解 GraphQL,这里是其文档,非常棒!
与 React Code 集成
由于本教程主要侧重于将 Hasura 和 GraphQL 与现有应用程序集成,因此我们将从一些预先编写的 React 代码开始。这个仓库包含我们将要构建的代码。目前,它是一个静态版本的绘图应用程序。一个人可以创作像素画,但它不连接到后端,因此绘图无法持久化,人们也无法协作绘图。
如果你克隆了代码库,请运行npm install
并安装所有依赖项。快速浏览一下代码,看看发生了什么。
Apollo 设置
我们将使用Apollo来更轻松地编写前端 GraphQL 连接。
在Connection.js
文件中添加以下代码:
import { HttpLink } from "apollo-link-http";
import { ApolloClient } from "apollo-client";
import { InMemoryCache } from "apollo-cache-inmemory";
export default new ApolloClient({
cache: new InMemoryCache(),
link: new HttpLink({
uri: "your-endpoint.herokuapp.com",
}),
});
对于uri
,请使用选项卡顶部的 GraphQL 端点GraphIQL
。
这将设置 Apollo 客户端,以便我们的 GraphQL 查询将指向我们创建的端点。
我们还需要添加几行代码index.js
。
import React from "react";
import ReactDOM from "react-dom";
+import { ApolloProvider } from "@apollo/react-hooks";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import connection from "./Connection";
ReactDOM.render(
+ <ApolloProvider client={connection}>
<App />
+ </ApolloProvider>,
document.getElementById("root")
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
这使我们的整个应用程序能够访问我们创建的 GraphQL 连接。现在我们的查询将自动转到正确的位置。
查询设置
每次访问应用程序时,我们都需要访问 GraphQL 端点,以获取每个像素应该使用的颜色。我们将在App.js
文件中添加一些代码,以使应用程序获取我们创建的数据,而不是现在正在使用的静态数据!
首先,我们将导入gql
模板标签。这将允许我们在 JavaScript 代码中编写 GraphQL 查询。我们将使用之前编写的查询来获取所有像素。
const GET_PIXELS = gql`
query GetPixels {
pixels(order_by: { id: asc }) {
color
id
}
}
`;
然后,我们将使用useQuery
Apollo 提供的钩子来获取我们的数据。
const { loading, error, data } = useQuery(GET_PIXELS);
当我们的组件加载时,这段代码将运行我们的查询。
这个钩子给了我们三个值:查询是否仍在运行(loading
)、如果存在错误消息以及从查询返回的数据。
在我们取回数据之前,我们可能需要某种加载指示器,因此我们将向组件添加一个条件:
if (loading) {
return <h2>Loading...</h2>;
}
我们还将更改map
为使用实时数据,而不是我们当前在第 5 行创建的硬编码像素。
data.pixels.map((pixel) => (
<Pixel {...pixel} key={pixel.id} newColor={color} />
));
总而言之,我们的改变如下App.js
:
import React, { useState } from "react";
+ import { useQuery } from "@apollo/react-hooks";
+ import gql from "graphql-tag";
import Pixel from "./Pixel";
import ColorPicker from "./ColorPicker";
- const pixels = new Array(400).fill("white");
+ const GET_PIXELS = gql`
+ query GetPixels {
+ pixels(order_by: { id: asc }) {
+ color
+ id
+ }
+ }
+`;
function App() {
+ const { loading, error, data } = useQuery(GET_PIXELS);
const [color, changeColor] = useState("white");
+ if (loading) {
+ return <h2>Loading...<h2/>;
+ }
return (
<div className="content">
<div className="logo">Draw</div>
<p>Pick a Color</p>
<ColorPicker changeColor={changeColor} />
<p>Click a Pixel</p>
<div className="container">
+ {data.pixels.map(pixel => (
+ <Pixel {...pixel} key={pixel.id} newColor={color} />
+ ))}
</div>
</div>
);
}
export default App;
变异设置
现在,让我们让Pixel
组件在点击像素时运行突变操作,从而改变像素的颜色。这将使我们的更改在各个用户和会话之间保持一致。
我们将再次使用我们的gql
模板标签,并将我们的变异放入其中。
const UPDATE_COLOR = gql`
mutation ChangePixelColor($id: Int!, $color: String!) {
update_pixels(where: { id: { _eq: $id } }, _set: { color: $color }) {
returning {
x
y
color
id
}
}
}
`;
Apollo 也有一个useMutation
钩子,所以我们将导入它并使用它。
const [updatePixelColor] = useMutation(UPDATE_COLOR);
onClick
当用户点击像素时,我们还将更新我们的处理程序以运行我们的变异。
onClick={() => {
changeColor(color);
updatePixelColor({ variables: { id, color: newColor } });
}}
Pixel.js
完成转换后,我们的意志将呈现如下面的样子:
import React from "react";
+ import gql from "graphql-tag";
+ import { useMutation } from "@apollo/react-hooks";
+ const UPDATE_COLOR = gql`
+ mutation ChangePixelColor($id: Int!, $color: String!) {
+ update_pixels(where: { id: { _eq: $id } }, _set: { color: $color }) {
+ returning {
+ color
+ id
+ }
+ }
+ }
+ `;
const Pixel = ({ id, color, newColor }) => {
+ const [updatePixelColor] = useMutation(UPDATE_COLOR);
return (
<span
className="pixel"
onClick={() => {
changeColor(color);
+ updatePixelColor({ variables: { id, color: newColor } });
}}
style={{ backgroundColor: color }}
></span>
);
};
export default Pixel;
哇!🙌🏻 现在我们的应用程序连接到了 GraphQL 端点并拉取了正确的数据。如果你想查看完整的解决方案代码,这里是它!
实现实时
目前,我们的应用程序在页面加载时会从 GraphQL 端点拉取数据,但当其他用户点击像素时,它不会实时更新。我们希望用户能够与朋友实时绘画。您可以尝试在两个标签页中打开已部署的应用程序——如果您在一个标签页中更新像素,另一个标签页也会更新。
我们只需要更新我们的App.js
以使用 GraphQL 订阅而不是查询。
我们将使用 Apollo 的useSubscription
钩子代替 useQuery,并将query
查询中的单词更改为subscription
。神奇🧙🏻♂️!
这是差异图,显示了哪些地方发生了变化!(注:由于变化不大,因此省略了大部分文件)
import React, { useState } from "react";
+ import { useSubscription } from "@apollo/react-hooks";
import gql from "graphql-tag";
import Pixel from "./Pixel";
import ColorPicker from "./ColorPicker";
const pixels = new Array(400).fill("white");
const GET_PIXELS = gql`
+ subscription GetPixels {
pixels(order_by: { id: asc }) {
color
id
}
}
`;
function App() {
const [color, changeColor] = useState("white");
+ const { loading, error, data } = useSubscription(GET_PIXELS);
...
这是包含订阅的完整代码!
后续步骤
-
您可以使用Hasura Actions添加一些自定义后端游戏逻辑。
-
如果您想了解有关 GraphQL 的更多信息,这里有一个很棒的课程。
-
如果您想深入了解使用 Hasura 构建后端,这里还有另一门课程。
-
Hasura 的网站上还有大量其他教程。
结论
使用这个堆栈构建应用程序让我非常享受——它让我可以专注于简单应用程序的前端代码。我可以创建一个允许两个用户实时交互的应用程序,并且几乎没有任何阻碍。传统上,使用 GraphQL 层编写完整的后端是一个相当繁琐的过程,需要大量的维护。有了 Hasura,我们只需点击几下鼠标就能完成。这是我构建快速应用程序的新首选堆栈。
另外,如果您想观看,这里还有视频版本!
迫不及待地想看看您创造了什么!
文章来源:https://dev.to/aspittel/hasura-101-building-a-realtime-game-with-graphql-postgres-and-react-5c3j