使用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是什么?

top_next.png

这个可能不需要解释,ZEIT公司开发的React框架可以构建支持服务器端渲染的web应用程序。

pages目录下的自动路由、动态路由,以及从SPA/SSR开始的静态网站生成应用,当然还包括最近的零配置TypeScript支持、AMP适配、通过api目录实现api等功能,大大提升了开发体验。当然,由于其简单易用的特点,任何人都可以轻松引入并显著提高开发速度。

AWS Amplify是什么?

top_amplify.png

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 页面。

スクリーンショット 2019-12-15 18.17.02.png

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用户。如果已有账户,请登录;如果没有,则创建一个新账户。

aws_account.png
スクリーンショット 2019-11-09 3.09.57.png

将accessKeyId和secretAccessKeyId按顺序粘贴到IAM用户创建页面上。

スクリーンショット 2019-12-15 18.37.54.png

当你进入IAM用户创建页面并进行操作时,会显示访问密钥ID。将其复制到控制台中,并按下Enter键。然后确定配置文件名称,按下Enter键后,将会出现”成功设置新用户”的消息,表示完成。

使项目可以处理Amplify。

我将逐步解释如何在实际中使用Next.js和amplify。为了能够处理amplify后端的各种服务,我们将在项目文件夹中创建各种资源。请尝试输入以下命令。

amplify init

我们将以互动形式进行以下操作:选择语言、框架和目录信息等。

スクリーンショット 2019-11-09 3.47.46.png

在云端初始化项目…当出现”初始化云端项目”消息时,开始初始化并准备后端资源,请等待。
当出现”您的项目已成功初始化并与云端连接!”消息时,表明准备工作已完成。

准备后端API(Graphql)

为了准备API,接下来输入以下命令。

amplify add api

那么,我们将以互动的方式确定在项目中使用的API种类、名称和模式配置。这次我们将使用GraphQL。以下是一些示例问题。

スクリーンショット 2019-12-15 18.49.16.png
スクリーンショット 2019-12-15 18.52.35.png

如果成功编译GraphQL模式,则会生成用于执行Graphql的api所需的文件等。之后,如果您想对模式文件进行更改,您可以自行进行编辑。本次将采用以下格式的模式。

type Todo @model {
  id: ID!
  description: String
  isDone: Boolean
}

接下来,使用以下命令进行部署。

amplify push

在这里,我们会以互动的方式继续提出问题。

スクリーンショット 2019-12-15 15.40.57.png

当部署完成后,后端资源会自动生成。

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操作相对应,具体如下:

昨日CRUDgraphql作成CREATEMutation取得READQuery更新UPDATEMutation削除DELETEMutation

此外,在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;

スクリーンショット 2019-12-15 20.52.26.png

在该状态下,使用npm run dev命令启动服务器,并尝试访问/todo,如果显示出Todo,则表示成功。

スクリーンショット 2019-12-15 20.50.52.png

总结和感想

非常感谢您仔细阅读到最后一句话。

在巷子里,无服务器的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文章的链接。

广告
将在 10 秒后关闭
bannerAds