使用Next.js + AWS Amplify + Graphql搭建无服务器应用程序环境
概述
你好,我是柯麦,最近在东京担任前端工程师。我对在AWS的re:Invent上发布的AWS Amplify DataStore感到非常震惊和方便,以前主要在Vue和Nuxt上开发,对React和Next的开发经验较少,也在寻找一些可以用Typescript完成的东西。
最近,AWS推出了一项名为Amplify的服务,类似于AWS版的Firebase,它集成了用于移动开发的AWS服务。看了一下内容,感觉可以用它来快速开发无服务器应用。于是我试着用Next.js + Amplify来制作了一个Todo应用程序。虽然市面上有很多关于Nuxt.js + Firebase组合的文章,但我也成功地用这种架构实现了,并且把方法整理成了体系化的描述。
希望能帮助你更好地理解Next + Amplify的开发,希望你能读到最后,谢谢!
Next.js是什么?
这个可能不需要解释,ZEIT公司开发的React框架可以构建支持服务器端渲染的web应用程序。
pages目录下的自动路由、动态路由,以及从SPA/SSR开始的静态网站生成应用,当然还包括最近的零配置TypeScript支持、AMP适配、通过api目录实现api等功能,大大提升了开发体验。当然,由于其简单易用的特点,任何人都可以轻松引入并显著提高开发速度。
AWS Amplify是什么?
AWS Amplify是一种基于AWS服务的mBaas,它可以极大地简化Web应用程序的创建、配置和开发,并且由于可扩展性强,可以根据服务规模自动扩展。类似的服务还有谷歌的Firebase。即使不需要自己准备后端资源,Amplify命令也可以为您准备所需的源代码和模型等。通常情况下,您需要自行处理配置、供应和分析等。但选择了Amplify后,它将为您管理这些事项。根据需求,Amplify可以将身份验证、离线数据、分析、推送通知、AR/VR、机器人等集成到应用程序中的AWS服务中。
Next.js + Amplify有什么令人高兴的地方?
初期开发速度的爆炸性加快
近来,市场变化非常快,用户需求也在快速变化。与此同时,在服务和产品的用户体验方面,我们很难确定什么是适当的。在这种情况下,业务方和开发方必须共同认识到的一点是,需要快速进行假设验证,并将反馈转化为用户体验。我写了一篇关于在DX时代及其未来掌握“用户体验”基础的文章,指出不仅在服务上线时需要注意,验证该过程也需要保持快速。因此,在技术选择和架构方面可能会有困惑,但从如何快速实现社会实施的观点来看,这本身就是一个应该尽快解决的问题。当然,这并不是应该轻视的问题。
在这种情况下,我们认为Next.js + Amplify的组合可以解决这个问题。与自行设计并选择AWS服务进行构建相比,这两者的环境设置所需的时间更短,可以立即在本地主机上进行确认,并在需要时提供后端服务。
定義了合适的类型和模型之后,自动缩放。
在Next.js中,您可以使用零配置来运行Typescript,并且在Amplify中包含了名为AppSync的服务,用于在Graphql中进行数据交换。因此,它会为您定义所需数据的类型,使得在前端和后端之间共享类型变得更加容易。
与框架相比,把握最重要的业务规则比什么框架更重要,一旦定义了这些规则,就可以根据需求进行自动缩放,从而确保数据的完整性并根据需要进行调整。
Next.js的配置
我們馬上開始進行Next.js的設置。
目录结构
最终的目录结构如下所示。实际的目录中只列出了必要的部分,如果您想查看所有文件,请在GitHub上准备好代码并参考。有关amplify和graphql目录,将在后续的AWS Amplify设置中自动生成。
├─ amplify
│ ├─ #current-cloud-backend
│ │ ├─ amplify-meta.json
│ │ ├─ api
│ │ │ └ todo
│ │ │ ├─ build
│ │ │ ├─ parameters.json
│ │ │ ├─ resolvers
│ │ │ ├─ schema.graphql
│ │ │ ├─ stacks
│ │ │ └─ transform.conf.json
│ │ └─ backend-config.json
│ ├─ backend
│ │ ├─ amplify-meta.json
│ │ ├─ api
│ │ │ └ todo
│ │ │ ├─ build
│ │ │ ├─ parameters.json
│ │ │ ├─ resolvers
│ │ │ ├─ schema.graphql
│ │ │ ├─ stacks
│ │ │ └─ transform.conf.json
│ │ ├─ awscloudformation
│ │ │ └ nested-cloudformation-stack.yml
│ │ └─ backend-config.json
│ └─ team-provider-info.json
├─graphql/
│ ├─queries.ts
│ ├─mutations.ts
│ ├─subscriptions.ts
│ └─schema.json
├─pages/
│ ├─index.tsx
│ └─todo.tsx
├─components/
│ └─templates/
│ ├─head.tsx
│ └─navigation.tsx
├─store/
├─aws-exports.js
├─package.json
├─.gitignore
├─next.config.js
├─next-env.d.ts
└─tsconfig.json
安装必要的模块并编辑package.json
当工作目录建立好后,我们用以下命令来准备所需的模块。
npm install --save react react-dom next
npm install --save-dev @types/node @types/react
在安装完成后,我们需要将package.json中的scripts编辑为以下内容。
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
}
准备各种组件
在这里我们将定义共同使用的组件。我们将准备导航和头部组件。
import * as React from 'react';
import Link from 'next/link'
const Navigation: React.FC = () => {
return (
<div>
<Link href="/">
<p>Index</p>
</Link>
<Link href="/about">
<p>About</p>
</Link>
<Link href="/todo">
<p>Todo</p>
</Link>
</div>
)
}
export default Navigation
请根据您的喜好设置头部用的组件。
import * as React from 'react'
import Head from 'next/head'
import info from '../../package.json'
const defaultOGURL = ''
const defaultOGImage = ''
interface Props {
title: string,
description?: string,
url?: string,
ogImage?: string,
}
const head: React.FC<Props> = props => {
return (
<Head>
<meta charSet="UTF-8" />
<title>{props.title || ''}</title>
<meta name="description" content={props.description || info.description} />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta property="og:url" content={props.url || defaultOGURL} />
<meta property="og:title" content={props.title || ''} />
<meta
property="og:description"
content={props.description || info.description}
/>
<meta name="twitter:site" content={props.url || defaultOGURL} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content={props.ogImage || defaultOGImage} />
<meta property="og:image" content={props.ogImage || defaultOGImage} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<link rel="icon" sizes="192x192" href="/static/touch-icon.png" />
<link rel="apple-touch-icon" href="/static/touch-icon.png" />
<link rel="mask-icon" href="/static/favicon-mask.svg" color="#49B882" />
<link rel="icon" href="/static/favicon.ico" />
</Head>
)
};
export default head
准备Pages
接下来是对 pages 组件进行准备。
import * as React from 'react';
import Head from '../components/templates/head'
import Navigation from '../components/templates/navigation'
const Index: React.FC = () => {
return (
<div>
<Head title="Index page" />
<Navigation />
<p>Hello world</p>
<p>Index</p>
</div>
)
}
export default Index
import * as React from "react";
import Head from '../components/templates/head'
import Navigation from '../components/templates/navigation'
const Todo = () => {
return (
<div>
<Head title="todo" />
<Navigation />
<h2>Todo with amplify</h2>
</div>
)
};
export default Todo;
现在运行 npm run dev 命令,并尝试访问 http://localhost:3000/。如果显示以下类似的页面,则表示成功。同时,试试访问 /todo,看看是否显示了 todo 页面。
Next.js的准备工作已经超过了一次。
在中国,只需要一个选项,将以下内容翻译成中文即可:
设置 AWS Amplify。
Amplify的安装和初始设置
无论如何,我们要进行amplify/cli的全局安装。
npm install -g @aws-amplify/cli
安装完成后,请使用amplify -v命令验证版本。如果显示如下内容,则表示安装已完成。
Scanning for plugins...
Plugin scan successful
3.17.0
下一步,将进行AWS账户的关联。请在控制台上进行操作。
amplify configure
打开终端并运行“yarn start”命令,将会启动浏览器以创建IAM用户。如果已有账户,请登录;如果没有,则创建一个新账户。
将accessKeyId和secretAccessKeyId按顺序粘贴到IAM用户创建页面上。
当你进入IAM用户创建页面并进行操作时,会显示访问密钥ID。将其复制到控制台中,并按下Enter键。然后确定配置文件名称,按下Enter键后,将会出现”成功设置新用户”的消息,表示完成。
使项目可以处理Amplify。
我将逐步解释如何在实际中使用Next.js和amplify。为了能够处理amplify后端的各种服务,我们将在项目文件夹中创建各种资源。请尝试输入以下命令。
amplify init
我们将以互动形式进行以下操作:选择语言、框架和目录信息等。
在云端初始化项目…当出现”初始化云端项目”消息时,开始初始化并准备后端资源,请等待。
当出现”您的项目已成功初始化并与云端连接!”消息时,表明准备工作已完成。
准备后端API(Graphql)
为了准备API,接下来输入以下命令。
amplify add api
那么,我们将以互动的方式确定在项目中使用的API种类、名称和模式配置。这次我们将使用GraphQL。以下是一些示例问题。
如果成功编译GraphQL模式,则会生成用于执行Graphql的api所需的文件等。之后,如果您想对模式文件进行更改,您可以自行进行编辑。本次将采用以下格式的模式。
type Todo @model {
id: ID!
description: String
isDone: Boolean
}
接下来,使用以下命令进行部署。
amplify push
在这里,我们会以互动的方式继续提出问题。
当部署完成后,后端资源会自动生成。
Next.js和Amplify的整合
当满足了必需的后端资源,我们将开始进行Next.js的实现。
首先,我们要安装所需的npm模块。
npm install --save @aws-amplify/api @aws-amplify/core @aws-amplify/pubsub
接下来,我们将编辑在设置Next.js时创建的pages/todo.tsx文件。
基本上,您需要在代码中导入amplify模块并进行配置处理。当您想要编写amplify的处理时,通常会在页面中执行以下操作。
import Amplify from '@aws-amplify/core';
import PubSub from '@aws-amplify/pubsub';
import API, { graphqlOperation } from '@aws-amplify/api';
import awsmobile from '../aws-exports';
Amplify.configure(awsmobile);
API.configure(awsmobile);
PubSub.configure(awsmobile);
接下来,我们将确保可以使用我们使用的后端API。由于我们正在使用GraphQL进行开发,因此我们将从中导入查询、变异和订阅。虽然我们在这里导入了所有内容,但实际上只需要导入需要使用的部分即可。
import { createTodo, deleteTodo, updateTodo } from '../graphql/mutations';
import { getTodo, listTodos } from '../graphql/queries';
import { onCreateTodo, onUpdateTodo, onDeleteTodo } from '../graphql/subscriptions';
简单解释一下,Query和Mutations可以与传统的CRUD操作相对应,具体如下:
此外,在GraphQL中还有一种称为Subscription(订阅)的功能。简单来说,这类似于来自服务器端的推送。当后端数据发生变化时,它可以检测并通知我们知道新的值。
如何使用查询功能
export const getTodo = `query GetTodo($id: ID!) {
getTodo(id: $id) {
id
description
isDone
}
}
`;
通过使用API.graphql(graphqlOperation)访问以以下方式导入的listTodos查询。当通信成功时,获取的值将存储在data中,您可以通过props传递它,或者将其传递给用于列表呈现的本地状态,以在React中进行呈现。
Todo.getInitialProps = async (props) => {
const data = await API.graphql(graphqlOperation(listTodos));
return {...props, ...data};
};
Mutations的使用方法
export const createTodo = `mutation CreateTodo($input: CreateTodoInput!) {
createTodo(input: $input) {
id
description
isDone
}
}
`;
这是用于注册的功能。在API.graphql(graphqlOperation(createTodo,inputData))中,将Mutation作为第一个参数,将要注册的数据放入第二个参数。
const submitTodo = async (list: Array<string>, todo: string) => {
const id = Math.floor(Math.random() * Math.floor(1000))
const inputData = {
input: {
id,
description: todo,
isDone: false
}
}
try {
await API.graphql(graphqlOperation(createTodo, inputData));
} catch (e) {
console.log(e);
}
};
订阅功能的使用方法
export const onCreateTodo = `subscription OnCreateTodo {
onCreateTodo {
id
description
isDone
}
}
`;
API.graphql(graphqlOperation(onCreateTodo)).subscribe({
next: e => {
// 購読する値の取得
const todo = e.value.data.onCreateTodo
// 値をセットする処理を書く
...
}
})
实现Todo应用程序
以下是本次实现的例子。
import * as React from "react";
import { useState } from 'react';
import Amplify from '@aws-amplify/core';
import PubSub from '@aws-amplify/pubsub';
import API, { graphqlOperation } from '@aws-amplify/api';
import awsmobile from '../aws-exports';
import {
createTodo,
deleteTodo,
updateTodo
} from '../graphql/mutations';
import { getTodo, listTodos } from '../graphql/queries';
import { onCreateTodo, onUpdateTodo, onDeleteTodo } from '../graphql/subscriptions';
import Head from '../components/templates/head'
import Navigation from '../components/templates/navigation'
Amplify.configure(awsmobile);
API.configure(awsmobile);
PubSub.configure(awsmobile);
interface TodoType {
id: number,
description: string
isDone: boolean
}
interface DataProp {
data: {
listTodos?: {
items: Array<TodoType>
}
}
}
const Todo = (props: DataProp) => {
const { items: todoItems } = props.data.listTodos;
const [todo, setTodo] = useState('');
const [list, setList] = useState([]);
// 新規追加でTodoを追加する
const submitTodo = async (list: Array<string>, todo: string) => {
const id = Math.floor(Math.random() * Math.floor(1000))
const inputData = {
input: {
id,
description: todo,
isDone: false
}
}
try {
await API.graphql(graphqlOperation(createTodo, inputData));
} catch (e) {
console.log(e);
}
};
// 既存のTodoを削除する
const deleteItem = async (id) => {
const deleteData = {
input: {
id
}
}
try {
await API.graphql(graphqlOperation(deleteTodo, deleteData));
} catch (e) {
console.log(e);
}
};
return (
<div>
<Head title="todo" />
<Navigation />
<h2>Todo with amplify</h2>
<input style={{
border: 'solid 1px #ddd',
padding: 10,
borderRadius: 4,
fontSize: 18,
WebkitAppearance: 'none',
color: '#333'
}} value={todo} type="text" placeholder="please write todo" onChange={e => setTodo(e.target.value)} />
<button style={{
padding: 10,
background: '#F06292',
color: '#eee',
borderRadius: 4,
fontSize: 18,
WebkitAppearance: 'none'
}} onClick={() => submitTodo(list, todo)}>add Todo</button>
<ul className="ListContainer">{
todoItems.map( item => (
<li key={item.id} className="ListItem">
<span className="title">{item.description}</span>
<span>{item.isDone}</span>
<input type="button" value="delete" onClick={() => deleteItem(item.id)} />
</li>
))
}</ul>
</div>
)
};
Todo.getInitialProps = async (props) => {
const data = await API.graphql(graphqlOperation(listTodos));
try {
const client = API.graphql(graphqlOperation(onCreateTodo));
if ("subscribe" in client) {
client.subscribe({
next: e => {
console.log(e);
}
});
}
} catch (e) {
console.error(e);
}
return {...props, ...data};
};
export default Todo;
在该状态下,使用npm run dev命令启动服务器,并尝试访问/todo,如果显示出Todo,则表示成功。
总结和感想
非常感谢您仔细阅读到最后一句话。
在巷子里,无服务器的Web应用程序开发可能非常流行,Nuxt.js + Firebase的信息非常丰富。它非常方便,也是我喜欢的一种技术。但相反,Next.js并不像以前那样常见,但随着版本升级,它增加了很多有吸引力的功能。另外,由于是基于React,与Vue相比,我觉得它更容易出问题,所以我认为Next.js也相当不错。
AWS Amplify本身也是由许多AWS服务组成的,因此除了易于使用之外,我认为它在扩展性和SLA方面也非常推荐。
然而,在任何技术选择中,我认为快速地进行假设验证并将反馈回馈给用户体验是很重要的。作为其中一个非常有吸引力的选择,我希望能逐渐地在将来开始使用它。
请参考
https://aws-amplify.github.io/docs/js/api 的内容是有关JavaScript的Amplify库的文档。
https://qiita.com/G-awa/items/a5b2cc7017b1eceeb002 是一个关于G-awa的Qiita文章的链接。