Next.js - 数据故事
很棒的帖子
在我之前的文章中,我介绍了如何使用 Next.js 和 AWS 深入实现身份验证。
在这篇文章中,我们将采取下一步措施来讨论数据故事、它如何融入画面,以及如何在有和没有身份验证和授权的情况下实现各种访问模式。
在这篇文章中,您将构建一个博客应用程序,该应用程序将支持公共和私有数据访问 - 在客户端以及服务器和 API 路由上获取数据。
该应用程序的最终代码位于此处
概述
在向 API 发出请求时,您通常需要处理安全性问题——管理ID 令牌、访问令牌和刷新令牌,以及根据用户会话(或非会话)维护应用程序和 UI 状态。您还经常需要为数据层设置公共和私有 API 访问权限。
结合访问控制、身份验证和授权通常很难正确且安全地进行。
了解如何启用和混合授权模式可以让您在构建现代应用程序时具有灵活性——其中大多数应用程序需要多种授权模式以及数据访问模式。
使用 Next.js 时,您通常会从客户端、服务器和 API 路由组合进行 API 调用。随着上一篇文章中介绍的SSR 支持的最新发布,Amplify 现在支持的功能之一就是在客户端和服务器上无缝集成所有这些机制。
当通过 REST 或 GraphQL 进行 API 调用时,如果启用 SSR 模式,Amplify 现在会在客户端和服务器上自动配置并发送正确的授权标头(必要时)。
本教程旨在展示所有这些工作原理,并提供针对以下用例实现数据获取的分步指南:
- 进行公共客户端 API 调用
- 进行经过身份验证的客户端 API 调用
getStaticPaths使用公共 API 调用(通过和getStaticProps)对静态生成的页面进行补充- 从 SSR 或 API 路由进行经过身份验证的 API 调用
- 为公共 API 端点创建 API 路由到您的数据层
放大数据获取
使用 Amplify 创建或配置 AppSync GraphQL API 时,您可以启用多种授权模式(默认模式以及其他模式)。这使您的应用能够整合私有、公共或公私结合的访问权限。在本教程中,我们将介绍如何使用单个 GraphQL API 实现公私结合的访问权限。
创建 API 后,您可以使用默认授权模式或指定授权模式来调用 API。
以下是一些示例
使用默认授权模式的公共 API 调用(客户端、静态、SSR 和 API 路由):
import { API } from 'aws-amplify';
import { listPosts } from './graphql/queries';
const data = await API.graphql({
query: listPosts
});
指定自定义授权模式(客户端):
import { API } from 'aws-amplify';
import { listPosts } from './graphql/queries'
const data = await API.graphql({
query: listPosts,
authMode: "AMAZON_COGNITO_USER_POOLS"
});
使用授权标头(SSR)发出经过身份验证的请求:
import { withSSRContext } from 'aws-amplify';
import { listPosts } from './graphql/queries'
export async function getServerSideProps(context) {
const { API } = withSSRContext(context);
const data = await API.graphql({
query: listPosts,
authMode: "AMAZON_COGNITO_USER_POOLS"
});
// do stuff with data
}
使用授权标头(API 路由)发出经过身份验证的请求:
import { withSSRContext } from 'aws-amplify';
import { listPosts } from './graphql/queries'
export default function handler(req, res) {
const { API } = withSSRContext({ req });
const data = await API.graphql({
query: listPosts,
authMode: "AMAZON_COGNITO_USER_POOLS"
});
// do stuff with data
}
关于应用程序
在本教程中,我们将构建一个基本的博客应用。用户可以注册、创建帖子并评论帖子。未登录的用户只能查看帖子。
为了演示公共和私人访问,我们只允许已登录的用户创建或查看帖子评论。
入门
如果您已经完成了第 1 部分的应用程序构建,请继续创建 API。
如果没有,请按照以下步骤部署启用身份验证的 Next 应用:
1. 克隆仓库
git clone https://github.com/dabit3/next.js-authentication-aws.git
2. 进入目录并安装依赖项
cd next.js-authentication-aws
npm install
3.初始化Amplify项目
amplify init
4.部署认证服务
amplify push --y
5. 在本地运行应用程序
npm run dev
创建 API
接下来,使用api类别创建一个新的 GraphQL API:
amplify add api
? Please select from one of the below mentioned services: GraphQL
? Provide API name: nextapi
? Choose the default authorization type for the API: API key
? Enter a description for the API key: public
? After how many days from now the API key should expire: 365
? Do you want to configure advanced settings for the GraphQL API: Yes
? Configure additional auth types? Yes
? Choose the additional authorization types you want to configure for the API: Amazon Cognito User Pool
? Configure conflict detection? No
? Do you have an annotated GraphQL schema? N
? Choose a schema template: Single object with fields
? Do you want to edit the schema now? Y
出现提示时,请使用以下 GraphQL 模式。
放大/后端/api/nextapi/schema.graphql
| type Post @model | |
| @auth(rules: [ | |
| { allow: owner, ownerField: "username" }, | |
| { allow: public, operations: [read] } | |
| ]) { | |
| id: ID! | |
| name: String! | |
| content: String | |
| comments: [Comment] @connection(keyName: "commentsByPostId", fields: ["id"]) | |
| username: String | |
| createdAt: AWSDateTime | |
| } | |
| type Comment @model | |
| @key(name: "commentsByPostId", fields: ["postId"], queryField: "commentsByPostId") | |
| @auth(rules: [ | |
| { allow: owner, ownerField: "username" }, | |
| { allow: public, operations: [read] } | |
| ]) { | |
| id: ID! | |
| postId: ID! | |
| message: String! | |
| username: String | |
| } |
| type Post @model | |
| @auth(rules: [ | |
| { allow: owner, ownerField: "username" }, | |
| { allow: public, operations: [read] } | |
| ]) { | |
| id: ID! | |
| name: String! | |
| content: String | |
| comments: [Comment] @connection(keyName: "commentsByPostId", fields: ["id"]) | |
| username: String | |
| createdAt: AWSDateTime | |
| } | |
| type Comment @model | |
| @key(name: "commentsByPostId", fields: ["postId"], queryField: "commentsByPostId") | |
| @auth(rules: [ | |
| { allow: owner, ownerField: "username" }, | |
| { allow: public, operations: [read] } | |
| ]) { | |
| id: ID! | |
| postId: ID! | |
| message: String! | |
| username: String | |
| } |
根据此模式创建的 API 将允许我们保存和查询两种不同类型的数据:
- 可以公开查看但只能由帖子创建者编辑或删除的帖子。
- 可以公开查看但只能由评论创建者编辑或删除的评论。
该模式利用Amplify 的GraphQL Transform库来为s 和s 的创建、读取、更新、删除和列出操作生成查询和变异,以及为每个变异创建订阅和为每种类型创建数据库(DynamoDB)。PostComment
我们还指定了一个自定义数据访问模式,允许我们通过帖子 ID () 查询评论commentsByPostId。
要部署 API,请运行以下命令push:
amplify push --y
一旦你的 API 部署完毕,你现在就可以在你的应用程序中使用它了。
创建博客应用程序
我们要做的第一件事是创建一个可复用的 Amplify 配置,以启用 SSR(注意:这仅适用于部分 SSR 或 API 路由,客户端路由则不需要)。在应用根目录下创建一个名为configureAmplify.js的文件。
| // configureAmplify.js | |
| import Amplify from 'aws-amplify'; | |
| import config from './src/aws-exports'; | |
| Amplify.configure({ ...config, ssr: true }); |
| // configureAmplify.js | |
| import Amplify from 'aws-amplify'; | |
| import config from './src/aws-exports'; | |
| Amplify.configure({ ...config, ssr: true }); |
现在,我们可以在需要配置 Amplify 的任何地方导入它。
助手/checkUser.js
接下来,我们将创建一个可重复使用的 React hook,它将允许我们轻松管理所有组件和页面上的用户状态。
在项目根目录中创建一个名为helpers的文件夹,并在新文件夹中创建名为checkUser.js的文件。
| // helpers/checkUser.js | |
| import { useState, useEffect } from 'react'; | |
| import { Auth, Hub } from 'aws-amplify'; | |
| export default function checkUser() { | |
| const [user, setUser] = useState(null); | |
| useEffect(() => { | |
| checkUserAuth(); | |
| const unsubscribe = Hub.listen('auth', () => checkUserAuth()); | |
| return () => unsubscribe(); | |
| }, []) | |
| async function checkUserAuth() { | |
| try { | |
| const signedInUser = await Auth.currentAuthenticatedUser(); | |
| setUser(signedInUser); | |
| } catch (err) { setUser(null); }; | |
| } | |
| return user; | |
| } |
| // helpers/checkUser.js | |
| import { useState, useEffect } from 'react'; | |
| import { Auth, Hub } from 'aws-amplify'; | |
| export default function checkUser() { | |
| const [user, setUser] = useState(null); | |
| useEffect(() => { | |
| checkUserAuth(); | |
| const unsubscribe = Hub.listen('auth', () => checkUserAuth()); | |
| return () => unsubscribe(); | |
| }, []) | |
| async function checkUserAuth() { | |
| try { | |
| const signedInUser = await Auth.currentAuthenticatedUser(); | |
| setUser(signedInUser); | |
| } catch (err) { setUser(null); }; | |
| } | |
| return user; | |
| } |
此钩子将自动跟踪已登录的用户并允许我们根据此用户状态管理我们的 UI(显示和隐藏 UI)。
页面/index.js
现在我们将更新应用程序的主入口点以显示从 API 获取的帖子列表。
此页面将进行客户端 API 调用,从 GraphQL 后端获取帖子,并在组件加载时使用公共 API 数据访问进行渲染。我们使用Link组件的来源next/link和帖子的 ID 来导航到相应的路由/posts/${post.id}。
使用以下代码更新pages/index.js 。
| // pages/index.js | |
| import { useState, useEffect } from 'react'; | |
| import { API } from 'aws-amplify'; | |
| import Link from 'next/link'; | |
| import { css } from 'emotion'; | |
| import { listPosts } from '../src/graphql/queries'; | |
| export default function Home() { | |
| const [posts, updatePosts] = useState([]); | |
| useEffect(() => { | |
| fetchPosts(); | |
| }, []); | |
| async function fetchPosts() { | |
| const postData = await API.graphql({ query: listPosts }); | |
| updatePosts(postData.data.listPosts.items); | |
| } | |
| return ( | |
| <div className={containerStyle}> | |
| <h1>Posts</h1> | |
| { | |
| posts.map(post => ( | |
| <Link key={post.id} href={`/posts/[id]`} as={`/posts/${post.id}`}> | |
| <h2 className={linkStyle}>{post.name}</h2> | |
| </Link> | |
| )) | |
| } | |
| </div> | |
| ) | |
| } | |
| const containerStyle = css`width: 700px; margin: 0 auto;`; | |
| const linkStyle = css`cursor: pointer; padding: 10px 0px; border-bottom: 1px solid #ddd;`; |
| // pages/index.js | |
| import { useState, useEffect } from 'react'; | |
| import { API } from 'aws-amplify'; | |
| import Link from 'next/link'; | |
| import { css } from 'emotion'; | |
| import { listPosts } from '../src/graphql/queries'; | |
| export default function Home() { | |
| const [posts, updatePosts] = useState([]); | |
| useEffect(() => { | |
| fetchPosts(); | |
| }, []); | |
| async function fetchPosts() { | |
| const postData = await API.graphql({ query: listPosts }); | |
| updatePosts(postData.data.listPosts.items); | |
| } | |
| return ( | |
| <div className={containerStyle}> | |
| <h1>Posts</h1> | |
| { | |
| posts.map(post => ( | |
| <Link key={post.id} href={`/posts/[id]`} as={`/posts/${post.id}`}> | |
| <h2 className={linkStyle}>{post.name}</h2> | |
| </Link> | |
| )) | |
| } | |
| </div> | |
| ) | |
| } | |
| const containerStyle = css`width: 700px; margin: 0 auto;`; | |
| const linkStyle = css`cursor: pointer; padding: 10px 0px; border-bottom: 1px solid #ddd;`; |
页面/_app.js
接下来让我们用我们想要使用的新配置来更新导航。
| // pages/_app.js | |
| import '../styles/globals.css'; | |
| import { css } from 'emotion'; | |
| import Link from 'next/link'; | |
| import checkUser from '../helpers/checkUser'; | |
| import '../configureAmplify'; | |
| export default function MyApp({ Component, pageProps }) { | |
| const user = checkUser(); | |
| return ( | |
| <div> | |
| <nav className={navStyle}> | |
| <AppLink title="Home" path="/" /> | |
| <AppLink title="Profile" path="/profile" /> | |
| { user && <AppLink title="Create Post" path="/create-post" /> } | |
| </nav> | |
| <Component {...pageProps} /> | |
| </div> | |
| ) | |
| } | |
| const AppLink = ({ title, path }) => ( | |
| <Link href={path}> | |
| <span className={linkStyle}>{title}</span> | |
| </Link> | |
| ) | |
| const linkStyle = css` | |
| margin-right: 20px; | |
| cursor: pointer; | |
| ` | |
| const navStyle = css` | |
| display: flex; | |
| padding: 30px; | |
| border-bottom: 1px solid #ddd; | |
| ` |
| // pages/_app.js | |
| import '../styles/globals.css'; | |
| import { css } from 'emotion'; | |
| import Link from 'next/link'; | |
| import checkUser from '../helpers/checkUser'; | |
| import '../configureAmplify'; | |
| export default function MyApp({ Component, pageProps }) { | |
| const user = checkUser(); | |
| return ( | |
| <div> | |
| <nav className={navStyle}> | |
| <AppLink title="Home" path="/" /> | |
| <AppLink title="Profile" path="/profile" /> | |
| { user && <AppLink title="Create Post" path="/create-post" /> } | |
| </nav> | |
| <Component {...pageProps} /> | |
| </div> | |
| ) | |
| } | |
| const AppLink = ({ title, path }) => ( | |
| <Link href={path}> | |
| <span className={linkStyle}>{title}</span> | |
| </Link> | |
| ) | |
| const linkStyle = css` | |
| margin-right: 20px; | |
| cursor: pointer; | |
| ` | |
| const navStyle = css` | |
| display: flex; | |
| padding: 30px; | |
| border-bottom: 1px solid #ddd; | |
| ` |
新的导航将使用user状态来显示和隐藏创建新帖子的链接(/create-post),因为只有登录的用户才能这样做。
页面/帖子/[id].js
接下来,我们需要一种方法来使用动态路由来呈现每个单独的帖子。
为此,创建一个名为pages/posts的新文件夹,并在新文件夹中创建名为[id].js的文件。
该页面将利用getStaticPaths并getStaticProps在构建时获取数据,并根据帖子以编程方式在我们的应用程序中构建页面。
我们还将使用该fallback标志在构建时启用可用路径的预渲染,同时仍允许用户在运行时动态创建页面。
| // pages/posts/[id].js | |
| import { API } from 'aws-amplify'; | |
| import { getPost, listPosts } from '../../src/graphql/queries' | |
| import '../../configureAmplify'; | |
| import { useRouter } from 'next/router'; | |
| export default function Post({ post }) { | |
| const router = useRouter(); | |
| if (router.isFallback) return <div>Loading...</div> | |
| return ( | |
| <div> | |
| <h2>{post.name}</h2> | |
| <p>{post.content}</p> | |
| <span>By: {post.username}</span> | |
| </div> | |
| ) | |
| } | |
| export async function getStaticPaths() { | |
| const postData = await API.graphql({ query: listPosts }); | |
| const postIds = postData.data.listPosts.items.map(post => ({ params: { id: post.id } })); | |
| return { | |
| paths: postIds, fallback: true | |
| }; | |
| } | |
| export async function getStaticProps(context) { | |
| const id = context.params.id; | |
| const post = await API.graphql({ query: getPost, variables: { id } }); | |
| return { | |
| props: { | |
| post: post.data.getPost | |
| } | |
| } | |
| } |
| // pages/posts/[id].js | |
| import { API } from 'aws-amplify'; | |
| import { getPost, listPosts } from '../../src/graphql/queries' | |
| import '../../configureAmplify'; | |
| import { useRouter } from 'next/router'; | |
| export default function Post({ post }) { | |
| const router = useRouter(); | |
| if (router.isFallback) return <div>Loading...</div> | |
| return ( | |
| <div> | |
| <h2>{post.name}</h2> | |
| <p>{post.content}</p> | |
| <span>By: {post.username}</span> | |
| </div> | |
| ) | |
| } | |
| export async function getStaticPaths() { | |
| const postData = await API.graphql({ query: listPosts }); | |
| const postIds = postData.data.listPosts.items.map(post => ({ params: { id: post.id } })); | |
| return { | |
| paths: postIds, fallback: true | |
| }; | |
| } | |
| export async function getStaticProps(context) { | |
| const id = context.params.id; | |
| const post = await API.graphql({ query: getPost, variables: { id } }); | |
| return { | |
| props: { | |
| post: post.data.getPost | |
| } | |
| } | |
| } |
页面/创建帖子.js
最后,我们将在pages目录中创建一个名为create-post.js的新文件,允许登录用户创建新帖子。
一旦创建新帖子,该组件将以编程方式导航到新路线。
| // pages/create-post.js | |
| import { useState } from 'react'; | |
| import { AmplifySignOut, withAuthenticator } from '@aws-amplify/ui-react'; | |
| import { css } from 'emotion'; | |
| import { useRouter } from 'next/router'; | |
| import { createPost } from '../src/graphql/mutations'; | |
| import { API } from 'aws-amplify'; | |
| function CreatePost() { | |
| const [formState, updateFormState] = useState({ name: '', content: ''}); | |
| const router = useRouter(); | |
| function onChange(e) { | |
| e.persist(); | |
| updateFormState(state => ({ ...state, [e.target.name]: e.target.value })); | |
| } | |
| async function createPostMutation() { | |
| if (!formState.name || !formState.content) return; | |
| const { data } = await API.graphql({ | |
| query: createPost, variables: { input: formState }, authMode: "AMAZON_COGNITO_USER_POOLS" | |
| }); | |
| router.push(`/posts/${data.createPost.id}`); | |
| } | |
| return ( | |
| <div> | |
| <div className={formStyle}> | |
| <input | |
| placeholder="Post name" | |
| name="name" | |
| onChange={onChange} | |
| className={inputStyle} | |
| /> | |
| <textarea | |
| placeholder="Post content" | |
| name="content" | |
| onChange={onChange} | |
| className={inputStyle} | |
| /> | |
| <button onClick={createPostMutation} className={buttonStyle}>Create Post</button> | |
| </div> | |
| <AmplifySignOut /> | |
| </div> | |
| ) | |
| } | |
| const formStyle = css`{ display: flex; flex-direction: column; margin: 40px; }` | |
| const inputStyle = css`{ padding: 7px; margin-bottom: 8px; width: 700px; }` | |
| const buttonStyle = css`{ | |
| background-color: | |
| black; width: 400px; | |
| height: 40px; | |
| color: white; | |
| font-size: 16px; | |
| cursor: pointer; | |
| }` | |
| export default withAuthenticator(CreatePost); |
| // pages/create-post.js | |
| import { useState } from 'react'; | |
| import { AmplifySignOut, withAuthenticator } from '@aws-amplify/ui-react'; | |
| import { css } from 'emotion'; | |
| import { useRouter } from 'next/router'; | |
| import { createPost } from '../src/graphql/mutations'; | |
| import { API } from 'aws-amplify'; | |
| function CreatePost() { | |
| const [formState, updateFormState] = useState({ name: '', content: ''}); | |
| const router = useRouter(); | |
| function onChange(e) { | |
| e.persist(); | |
| updateFormState(state => ({ ...state, [e.target.name]: e.target.value })); | |
| } | |
| async function createPostMutation() { | |
| if (!formState.name || !formState.content) return; | |
| const { data } = await API.graphql({ | |
| query: createPost, variables: { input: formState }, authMode: "AMAZON_COGNITO_USER_POOLS" | |
| }); | |
| router.push(`/posts/${data.createPost.id}`); | |
| } | |
| return ( | |
| <div> | |
| <div className={formStyle}> | |
| <input | |
| placeholder="Post name" | |
| name="name" | |
| onChange={onChange} | |
| className={inputStyle} | |
| /> | |
| <textarea | |
| placeholder="Post content" | |
| name="content" | |
| onChange={onChange} | |
| className={inputStyle} | |
| /> | |
| <button onClick={createPostMutation} className={buttonStyle}>Create Post</button> | |
| </div> | |
| <AmplifySignOut /> | |
| </div> | |
| ) | |
| } | |
| const formStyle = css`{ display: flex; flex-direction: column; margin: 40px; }` | |
| const inputStyle = css`{ padding: 7px; margin-bottom: 8px; width: 700px; }` | |
| const buttonStyle = css`{ | |
| background-color: | |
| black; width: 400px; | |
| height: 40px; | |
| color: white; | |
| font-size: 16px; | |
| cursor: pointer; | |
| }` | |
| export default withAuthenticator(CreatePost); |
测试一下
我们现在应该可以测试它了。
npm run dev
您应该能够创建帖子、查看帖子和查看帖子列表。
添加评论
组件/Comments.js
接下来,让我们启用添加评论的功能。
为此,请在项目根目录下创建一个名为components的新文件夹,并在该目录中创建一个名为Comments.js的文件,其中包含以下代码。
| // components/Comments.js | |
| import React, { useState, useEffect } from 'react'; | |
| import { API } from 'aws-amplify'; | |
| import { commentsByPostId } from '../src/graphql/queries'; | |
| import { createComment } from '../src/graphql/mutations'; | |
| import checkUser from '../helpers/checkUser'; | |
| const initialState = { message: '' }; | |
| export default function Comments({ postId }) { | |
| const [comments, updateComments] = useState([]); | |
| const [formState, updateFormState] = useState(initialState); | |
| const user = checkUser(); | |
| useEffect(() => { | |
| fetchComments(); | |
| }, []); | |
| function onChange(e) { | |
| e.persist(); | |
| updateFormState({ message: e.target.value }); | |
| } | |
| async function fetchComments() { | |
| const commentData = await API.graphql({ query: commentsByPostId, variables: { postId }}) | |
| updateComments(commentData.data.commentsByPostId.items); | |
| } | |
| async function createCommentMutation() { | |
| if (!formState.message) return; | |
| const id = Math.random().toString(36).slice(-6); | |
| updateComments([...comments, { id, message: formState.message, username: user.username }]); | |
| await API.graphql({ | |
| query: createComment, | |
| variables: { input: { postId, message: formState.message }}, | |
| authMode: 'AMAZON_COGNITO_USER_POOLS' | |
| }); | |
| updateFormState(initialState); | |
| } | |
| return ( | |
| <div> | |
| <h3>Create Comment</h3> | |
| <input placeholder="Comment" onChange={onChange} value={formState.message} /> | |
| <button onClick={createCommentMutation}>Create Comment</button> | |
| <h3>Comments</h3> | |
| { | |
| comments.map(comment => ( | |
| <div key={comment.id}> | |
| <h3>{comment.message}</h3> | |
| <span>by: {comment.username}</span> | |
| </div> | |
| )) | |
| } | |
| </div> | |
| ) | |
| } |
| // components/Comments.js | |
| import React, { useState, useEffect } from 'react'; | |
| import { API } from 'aws-amplify'; | |
| import { commentsByPostId } from '../src/graphql/queries'; | |
| import { createComment } from '../src/graphql/mutations'; | |
| import checkUser from '../helpers/checkUser'; | |
| const initialState = { message: '' }; | |
| export default function Comments({ postId }) { | |
| const [comments, updateComments] = useState([]); | |
| const [formState, updateFormState] = useState(initialState); | |
| const user = checkUser(); | |
| useEffect(() => { | |
| fetchComments(); | |
| }, []); | |
| function onChange(e) { | |
| e.persist(); | |
| updateFormState({ message: e.target.value }); | |
| } | |
| async function fetchComments() { | |
| const commentData = await API.graphql({ query: commentsByPostId, variables: { postId }}) | |
| updateComments(commentData.data.commentsByPostId.items); | |
| } | |
| async function createCommentMutation() { | |
| if (!formState.message) return; | |
| const id = Math.random().toString(36).slice(-6); | |
| updateComments([...comments, { id, message: formState.message, username: user.username }]); | |
| await API.graphql({ | |
| query: createComment, | |
| variables: { input: { postId, message: formState.message }}, | |
| authMode: 'AMAZON_COGNITO_USER_POOLS' | |
| }); | |
| updateFormState(initialState); | |
| } | |
| return ( | |
| <div> | |
| <h3>Create Comment</h3> | |
| <input placeholder="Comment" onChange={onChange} value={formState.message} /> | |
| <button onClick={createCommentMutation}>Create Comment</button> | |
| <h3>Comments</h3> | |
| { | |
| comments.map(comment => ( | |
| <div key={comment.id}> | |
| <h3>{comment.message}</h3> | |
| <span>by: {comment.username}</span> | |
| </div> | |
| )) | |
| } | |
| </div> | |
| ) | |
| } |
该组件将呈现相关评论列表并让用户对帖子发表评论。
页面/帖子/[id].js
Comments接下来,如果用户通过身份验证,我们将更新帖子组件来呈现该组件。
| // pages/posts/[id].js | |
| import { API } from 'aws-amplify'; | |
| import { getPost, listPosts } from '../../src/graphql/queries' | |
| import '../../configureAmplify'; | |
| import { useRouter } from 'next/router'; | |
| import Comments from '../../components/Comments'; | |
| import checkUser from '../../helpers/checkUser'; | |
| export default function Post({ post }) { | |
| const router = useRouter(); | |
| const user = checkUser(); | |
| if (router.isFallback) return <div>Loading...</div> | |
| return ( | |
| <div> | |
| <h2>{post.name}</h2> | |
| <p>{post.content}</p> | |
| <span>By: {post.username}</span> | |
| { user && <Comments postId={post.id} /> } | |
| </div> | |
| ) | |
| } | |
| export async function getStaticPaths() { | |
| const postData = await API.graphql({ query: listPosts }); | |
| const postIds = postData.data.listPosts.items.map(post => ({ params: { id: post.id } })); | |
| return { | |
| paths: postIds, fallback: true | |
| }; | |
| } | |
| export async function getStaticProps(context) { | |
| const id = context.params.id; | |
| const post = await API.graphql({ query: getPost, variables: { id } }); | |
| console.log({ post: JSON.stringify(post) }); | |
| return { | |
| props: { | |
| post: post.data.getPost | |
| } | |
| } | |
| } |
| // pages/posts/[id].js | |
| import { API } from 'aws-amplify'; | |
| import { getPost, listPosts } from '../../src/graphql/queries' | |
| import '../../configureAmplify'; | |
| import { useRouter } from 'next/router'; | |
| import Comments from '../../components/Comments'; | |
| import checkUser from '../../helpers/checkUser'; | |
| export default function Post({ post }) { | |
| const router = useRouter(); | |
| const user = checkUser(); | |
| if (router.isFallback) return <div>Loading...</div> | |
| return ( | |
| <div> | |
| <h2>{post.name}</h2> | |
| <p>{post.content}</p> | |
| <span>By: {post.username}</span> | |
| { user && <Comments postId={post.id} /> } | |
| </div> | |
| ) | |
| } | |
| export async function getStaticPaths() { | |
| const postData = await API.graphql({ query: listPosts }); | |
| const postIds = postData.data.listPosts.items.map(post => ({ params: { id: post.id } })); | |
| return { | |
| paths: postIds, fallback: true | |
| }; | |
| } | |
| export async function getStaticProps(context) { | |
| const id = context.params.id; | |
| const post = await API.graphql({ query: getPost, variables: { id } }); | |
| console.log({ post: JSON.stringify(post) }); | |
| return { | |
| props: { | |
| post: post.data.getPost | |
| } | |
| } | |
| } |
测试一下
我们现在应该能够测试新的评论功能。
开发模式
npm run dev
运行构建
npm run build
npm start
部署到 AWS
确保您已serverless.yml在项目根目录下创建了一个具有以下配置的文件:
myNextApp:
component: "@sls-next/serverless-component@1.17.0"
然后运行以下命令:
npx serverless
注意 - 由于 Cloudfront 的工作方式,您的 URL 通常可能需要几分钟才能生效
公开 API
让我们看看如何启用一个公共 API,以便其他开发者通过他们的应用使用。为此,我们将在pages/api/posts.js创建一个新的 API 路由,代码如下:
| // pages/api/pages.js | |
| import { API } from 'aws-amplify'; | |
| import { listPosts } from '../../src/graphql/queries'; | |
| export default async (_, res) => { | |
| try { | |
| const postData = await API.graphql({ query: listPosts }); | |
| res.json({ posts: postData.data.listPosts.items}); | |
| } catch (err) { | |
| res.json({ error: true }); | |
| } | |
| } |
| // pages/api/pages.js | |
| import { API } from 'aws-amplify'; | |
| import { listPosts } from '../../src/graphql/queries'; | |
| export default async (_, res) => { | |
| try { | |
| const postData = await API.graphql({ query: listPosts }); | |
| res.json({ posts: postData.data.listPosts.items}); | |
| } catch (err) { | |
| res.json({ error: true }); | |
| } | |
| } |
现在您应该能够导航到http://localhost:3000/api/posts并查看包含帖子列表的 JSON 响应。
文章来源:https://dev.to/dabit3/next-js-the-data-story-2b0d该应用程序的最终代码位于此处
后端开发教程 - Java、Spring Boot 实战 - msg200.com