Auth.js と Upstash Redis を使用した Next.js での安全なユーザー認証
このチュートリアルでは、Auth.js と Upstash Redis を使用して Next.js アプリケーションにユーザー認証を実装する方法を学習します。必要な環境をセットアップし、ユーザーの安全なサインインとサインアップのためのカスタム認証プロバイダーを作成し、セッションを管理するためのデータベースとして Upstash Redis を統合する方法を学びます。
前提条件
以下が必要になります。
- Node.js 18 以降
- Upstash アカウント
技術スタック
Upstash Redis インスタンスをセットアップする
Upstash ダッシュボードで、Redis に移動します。 タブをクリックしてデータベースを作成します。

REST API セクションが見つかるまで下にスクロールし、.env を選択します。 ボタン。コンテンツをコピーし、安全な場所に保存します。

新しい Next.js アプリケーションを作成する
新しい Next.js プロジェクトを作成することから始めましょう。ターミナルを開いて次のコマンドを実行します。
npx create-next-app@latest my-app プロンプトが表示されたら、次のものを選択します。
YesTypeScript を使用するように求められた場合。NoESLint を使用するように求められた場合。YesTailwind CSS を使用するよう求められた場合。Nosrc/を使用するように求められた場合 ディレクトリ。YesApp Router を使用するように求められた場合。NoTurbopack を使用するように求められた場合。Noデフォルトのインポート エイリアス (@/*) をカスタマイズするよう求められた場合 ).
それが完了したら、プロジェクト ディレクトリに移動し、次のコマンドを実行してアプリを開発モードで開始します。
cd my-app
npm run dev アプリは localhost:3000 で実行されている必要があります。次のコマンドを使用して、開発サーバーを停止し、必要な依存関係をインストールします。
npm install @upstash/redis nanoid
npm install next-auth @auth/core @auth/upstash-redis-adapter The libraries installed include:
nanoid:一意の安全な ID を生成するためのライブラリ。next-auth:Next.js 用に構築された認証ソリューション。@auth/core:Auth.js で認証を処理するためのコア パッケージ。@upstash/redis:HTTP リクエスト経由で Upstash Redis と対話するための SDK。@auth/upstash-redis-adapter:Auth.js を Upstash Redis と統合するためのアダプター。
Now, create a .env file at the root of your project. You are going to use the AUTH_SECRET 、UPSTASH_REDIS_REST_URL と UPSTASH_REDIS_REST_TOKEN 値。
.env file should have the following:
# .env
# Auth.js Environment Variable
AUTH_SECRET="..."
# Upstash Redis Environment Variables
UPSTASH_REDIS_REST_URL="https://...upstash.io"
UPSTASH_REDIS_REST_TOKEN="..." Instantiate the Upstash Redis Client
Create a file named redis.ts lib 内 directory with the following code:
// File: lib/redis.ts
import { Redis } from '@upstash/redis'
export default new Redis({
url: process.env.UPSTASH_REDIS_REST_URL,
token: process.env.UPSTASH_REDIS_REST_TOKEN,
}) 上記のコードでは、Edge 互換の Redis クライアント インスタンスがエクスポートされ、アプリケーション全体で使用されます。
ユーザー タイプと認証情報ヘルパーを設定する
Create a file named types.ts lib 内 directory with the following code:
// File: lib/types.ts
export interface UserType {
id?: string
email: string
name?: string
image?: string
password?: string
emailVerified?: string
}
UserType インターフェイスは、アプリケーション内のユーザー オブジェクトの構造を定義します。これには以下が含まれます:
id(オプション):ユーザーの一意の識別子。email(required):The user's email address.name(optional):The user's name.image(オプション):ユーザーのプロフィール画像への URL またはパス。password(optional):The user's hashed password.emailVerified(オプション):ユーザーのメールが検証されたかどうか/いつ検証されたかを示すタイムスタンプまたは文字列。
このインターフェイスは、アプリケーション全体でユーザー データの型定義として機能します。
Further, create a file named credentials.ts lib 内 directory with the following code:
// File: lib/credentials.ts
export function generateRandomToken() {
const array = new Uint8Array(20)
crypto.getRandomValues(array)
return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('')
}
export function generateRandomString(inputValue: string) {
const encoder = new TextEncoder()
const data = encoder.encode(inputValue)
return crypto.subtle.digest('SHA-256', data).then((hashBuffer) => {
return Array.from(new Uint8Array(hashBuffer))
.map((byte) => byte.toString(16).padStart(2, '0'))
.join('')
})
}
export async function hashPassword(password: string) {
const encoder = new TextEncoder()
const data = encoder.encode(password)
const hashBuffer = await crypto.subtle.digest('SHA-256', data)
return Array.from(new Uint8Array(hashBuffer))
.map((byte) => byte.toString(16).padStart(2, '0'))
.join('')
}
export async function comparePassword(password: string, hash: string) {
const hashedPassword = await hashPassword(password)
return hashedPassword === hash
} 上記のコードでは、安全なトークンとハッシュされたパスワードを生成および管理するための Edge 互換ユーティリティ関数が定義されています。
generateRandomToken:20 バイトのランダム配列を生成し、それを 16 進文字列に変換することで、暗号的に安全なランダム トークンを作成します。generateRandomString:入力文字列を受け入れ、それをバイトにエンコードし、SHA-256 を使用してハッシュし、ハッシュを 16 進文字列として返します。入力値から派生した一貫性のある一意の文字列を生成するのに役立ちます。hashPassword:SHA-256 を使用してプレーンテキストのパスワードをハッシュし、結果のハッシュを 16 進文字列に変換します。これはパスワードを安全に保存するために使用されます。comparePassword:入力をハッシュし、保存されているハッシュと一致するかどうかを確認することで、平文のパスワードとハッシュされたパスワードを比較します。
Auth.js で Upstash Redis アダプターを使用する
auth.ts という名前のファイルを作成します。 lib 内 directory with the following code:
// File: lib/auth.ts
import { UpstashRedisAdapter } from '@auth/upstash-redis-adapter'
import NextAuth from 'next-auth'
import providers from './providers'
import redis from './redis'
export const { handlers, signIn, signOut, auth } = NextAuth(() => ({
providers,
session: { strategy: 'jwt' },
adapter: UpstashRedisAdapter(redis),
}))
上記のコードでは、handlers を作成することで認証が設定されています。 、signIn 、signOut 、auth カスタム構成を使用して機能します。 JWT ベースのセッション戦略である UpstashRedisAdapter を使用します。 ユーザー データを保存するための Upstash Redis データベースと、providers で定義された認証プロバイダーの配列に接続します。 モジュール。この設定により、バックエンドとして Upstash Redis を使用した認証とセッション管理のシームレスな統合が可能になります。
Auth.js を使用してカスタム認証プロバイダを設定する
providers.ts という名前のファイルを作成します。 lib 内 次のコードを含むディレクトリ:
// File: lib/providers.ts
import { nanoid } from 'nanoid'
import Credentials from 'next-auth/providers/credentials'
import { comparePassword, generateRandomString, hashPassword } from './credentials'
import redis from './redis'
import { UserType } from './types'
export default [
Credentials({
credentials: {
email: {},
password: {},
},
authorize: async (credentials, request) => {
let type
try {
const tmp = new URL(request.url).searchParams.get('kind')
if (tmp && typeof tmp === 'string') type = tmp
} catch (e) {}
if (!type || !credentials.email || typeof credentials.email !== 'string' || !credentials.password || typeof credentials.password !== 'string') return null
const randomizedPassword = await generateRandomString(credentials.password)
const userByEmail = await redis.get<string | null | undefined>(`user:email:${credentials.email}`)
if (userByEmail) {
if (type !== 'in') {
console.log(`can not sign in in a non sign-in mode.`)
throw new Error(`can not sign in in a non sign-in mode.`)
}
const user = await redis.get<UserType>(`user:${userByEmail}`)
if (!user) {
console.log(`Found the user by email from user:email:${userByEmail}, but the user object is missing at user:${userByEmail}`)
return null
}
if (user.password) {
const hashedPassword = await hashPassword(randomizedPassword)
const isPasswordCorrect = await comparePassword(user.password, hashedPassword)
if (isPasswordCorrect) {
const { password, ...rest } = user
return rest
}
throw new Error(`incorrect password for credentials.`)
}
throw new Error(`you are using some other authentication method already, but not credentials.`)
} else {
if (type !== 'up') {
console.log(`can not sign up in a non sign-up mode.`)
throw new Error(`can not sign up in a non sign-up mode.`)
}
const newUser = {
name: null,
image: null,
emailVerified: null,
email: credentials.email,
password: randomizedPassword,
}
const tmp = nanoid()
await redis.set(`user:email:${credentials.email}`, tmp)
await redis.set(`user:${tmp}`, newUser)
return newUser
}
},
}),
] 上記のコードでは、カスタムの資格情報ベースの認証プロバイダーの配列がエクスポートされます。資格情報プロバイダーは、サインインとサインアップの両方のロジックを処理します。ユーザーが Redis に存在するかどうかを確認し、パスワードを検証するか、新しいユーザーを作成します。サインインする場合、提供されたパスワード (ランダムな文字列を使用してハッシュ化されたもの) と保存されているパスワードが比較されます。サインアップすると、Upstash Redis に新しいユーザー エントリが作成されます。このコードは、ユーザー データ ストレージに Upstash Redis を使用し、URL の kind パラメーターに基づいてプロセスがサインアップ モードまたはサインイン モードで実行されるようにします。
認証 API ルートを設定する (Auth.js を利用)
route.ts という名前のファイルを作成します。 app/api/auth/[...nextauth] 内 次のコードを含むディレクトリ:
// File: app/api/auth/[...nextauth]/route.ts
export const runtime = 'edge'
import { handlers } from '@/lib/auth'
export const { GET, POST } = handlers
上記のコードでは、2 つのエンドポイント ハンドラー GET および POST handlers によって処理されるエクスポート @/lib/auth.ts からエクスポートされた関数 ファイル。
route.ts という名前のファイルを作成します。 app/api/refresh 内 次のコードを含むディレクトリ:
// File: app/api/refresh/route.ts
export const runtime = 'edge'
export const dynamic = 'force-dynamic'
export const fetchCache = 'force-no-store'
import { auth } from '@/lib/auth'
import redis from '@/lib/redis'
import { UserType } from '@/lib/types'
import { encode } from '@auth/core/jwt'
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'
export async function GET(request: Request) {
const useSecureCookie = request.url.startsWith('https:')
const salt = useSecureCookie ? '__Secure-authjs.session-token' : 'authjs.session-token'
if (!process.env.AUTH_SECRET) return new NextResponse(null, { status: 500 })
const [session, cookieStore] = await Promise.all([auth(), cookies()])
if (!session?.user?.email) return new NextResponse(null, { status: 400 })
const userByEmail = await redis.get(`user:email:${session.user.email}`)
const userData = await redis.get<UserType>(`user:${userByEmail}`)
if (!userData?.email) cookieStore.set(salt, toString(), { secure: useSecureCookie, path: '/', httpOnly: true, sameSite: 'lax', maxAge: 0 })
else {
const { image, password, ...rest } = userData
const saltVal = await encode({ salt, secret: process.env.AUTH_SECRET, token: { ...rest, picture: image } })
cookieStore.set(salt, saltVal, { secure: useSecureCookie, path: '/', httpOnly: true, sameSite: 'lax' })
}
return new NextResponse()
} 上記のコードでは、ユーザーのセッションを更新するための Edge 互換 API ルートが作成されています。 Redis からユーザー セッションと関連データを取得し、ユーザーの電子メールを検証し、セッション Cookie を更新します。ユーザーデータが無効または欠落している場合、セッション Cookie はクリアされます。それ以外の場合は、新しい署名付きトークンが生成され、Cookie に保存されます。
route.ts という名前のファイルを作成します。 app/api/user 内 次のコードを含むディレクトリ:
// File: app/api/user/route.ts
export const runtime = 'edge'
export const dynamic = 'force-dynamic'
export const fetchCache = 'force-no-store'
import { auth } from '@/lib/auth'
import redis from '@/lib/redis'
import { UserType } from '@/lib/types'
import { NextResponse } from 'next/server'
export async function POST(request: Request) {
try {
const session = await auth()
if (!session?.user?.email) return new NextResponse(null, { status: 400 })
const body = await request.json()
const userByEmail = await redis.get(`user:email:${session.user.email}`)
const userData = await redis.get<UserType>(`user:${userByEmail}`)
if (!userData) return new NextResponse(null, { status: 404 })
if (body.name) userData.name = body.name
if (body.image) userData.image = body.image
await redis.set(`user:${userByEmail}`, userData)
return new NextResponse()
} catch (e: any) {
const message = e.message || e.toString()
console.log(message)
return new NextResponse(message, { status: 500 })
}
} 上記のコードでは、ユーザー データを更新するための Edge 互換 API ルートが作成されています。ユーザーを認証し、電子メールを使用して Redis からデータを取得し、リクエスト本文に基づいてユーザーの情報 (名前や画像など) を更新します。ユーザーが見つからない場合、またはエラーが発生した場合は、適切なステータス コードと応答が返されます。
Auth.js を使用して Next.js ミドルウェアをセットアップする
middleware.ts という名前のファイルを作成します。 プロジェクトのルート ディレクトリに次のコードを追加します。
// File: middleware.ts
export { auth as middleware } from '@/lib/auth'
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
}
上記のコードでは、auth を再エクスポートすることでミドルウェアが作成されます。 @/lib/auth の関数 モジュール。ミドルウェアは、matcher で指定されているように、API ルート、静的ファイル、画像アセット、およびファビコンを除くすべてのルートに認証を適用します。 構成。これにより、除外されていないルートが Auth.js で設定された認証ロジックによって確実に保護されます。
Next.js App Router コンポーネントで Auth.js を使用する
NextAuthProvider.tsx という名前のファイルを作成します。 app 内 次のコードを含むディレクトリ:
// File: app/NextAuthProvider.tsx
'use client'
import { SessionProvider } from 'next-auth/react'
type Props = {
children?: React.ReactNode
}
export default function ({ children }: Props) {
return <SessionProvider>{children}</SessionProvider>
}
上記のコードでは、クライアント側の React コンポーネントが作成され、NextAuth の SessionProvider でアプリケーションをラップします。 。コンポーネントは children を受け入れます props として使用し、ラップされたコンポーネントが NextAuth によって提供されるユーザー セッション コンテキストにアクセスできるようにします。この設定は、Next.js でアプリケーション全体の認証状態を管理するために不可欠です。
csrf.tsx という名前のファイルを作成します。 app 内 次のコードを含むディレクトリ:
// File: app/csrf.tsx
'use client'
import { getCsrfToken } from 'next-auth/react'
import { useEffect, useState } from 'react'
export function CSRFInput() {
const [csrfToken, setCsrfToken] = useState<string>()
useEffect(() => {
getCsrfToken().then((res) => setCsrfToken(res))
}, [])
return <input type="hidden" name="csrfToken" defaultValue={csrfToken} />
}
上記のコードでは、NextAuth の getCsrfToken を使用して CSRF トークンを取得するクライアント側 React コンポーネントがエクスポートされます。 関数を作成し、それを状態変数に保存します。このコンポーネントは、CSRF トークンを含む非表示の入力フィールドをレンダリングし、トークンを非表示の値として含めることで安全なフォーム送信を可能にします。これは、クロスサイト リクエスト フォージェリ攻撃から保護するのに役立ちます。
provider.tsx という名前のファイルを作成します。 app 内 次のコードを含むディレクトリ:
// File: app/provider.tsx
'use client'
import { Button } from '@/components/ui/button'
import { Icon } from '@iconify/react'
import { signIn } from 'next-auth/react'
export default function ({ provider, prefix }: { prefix: string; provider: { name: string; id: string } }) {
return (
<Button onClick={() => signIn(provider.id)} key={provider.name} variant="outline" className="flex w-full gap-x-3">
{provider.id === 'google' && <Icon fontSize={18} icon="flat-color-icons:google" />}
<span className="text-black">
{prefix} with {provider.name}
</span>
</Button>
)
}
上記のコードでは、さまざまな認証プロバイダーのカスタマイズ可能なサインイン ボタンをレンダリングするための React コンポーネントがエクスポートされます。 provider を受け入れます (name を含む) と id ) と prefix 小道具として。ボタンは signIn をトリガーします 指定されたプロバイダーの関数を実行し、プロバイダーの id に基づいてアイコンとラベルを動的に表示します。 .
layout.tsx という名前のファイルを更新します。 app 内 次のコードを含むディレクトリ:
// File: app/layout.tsx
import './globals.css'
import NextAuthProvider from './NextAuthProvider'
export default function ({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en">
<body className="bg-white text-black antialiased">
<NextAuthProvider>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex flex-col">{children}</div>
</NextAuthProvider>
</body>
</html>
)
}
上記のコードでは、Next.js レイアウトがページ全体をグローバル スタイルと認証コンテキストでラップしています。 globals.css をインポートします グローバル スタイルと NextAuthProvider の場合 認証状態を管理します。 children prop は、ネストされたページ コンテンツを表します。これは、レスポンシブ パディングと最大幅を備えたスタイル付きコンテナーでラップされ、すべてのページに一貫したレイアウトを提供します。
クライアント側ページにユーザー認証状態をロードする
page.tsx という名前のファイルを更新します。 app 内 次のコードを含むディレクトリ:
// File: app/page.tsx
'use client'
import { signOut, useSession } from 'next-auth/react'
export default function () {
const { data, status } = useSession()
return (
<>
{JSON.stringify({ data, status })}
<button onClick={() => signOut()}>Sign Out</button>
</>
)
}
上記のコードでは、ユーザーのセッション データを表示し、「サインアウト」ボタンを提供するクライアント側の React コンポーネントがエクスポートされます。 NextAuth の useSession を使用します。 フックを使用してセッション データとステータスを取得し、JSON 文字列として表示します。 signOut ボタンをクリックすると関数が呼び出され、ユーザーがログアウトできるようになります。
動的なサインイン ページを作成する (Auth.js を使用)
page.tsx という名前のファイルを作成します。 app/signin 内 次のコードを含むディレクトリ:
// File: app/signin/page.tsx
export const runtime = 'edge'
import { CSRFInput } from '@/app/csrf'
import Provider from '@/app/provider'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { auth } from '@/lib/auth'
import providers from '@/lib/providers'
import { unstable_noStore } from 'next/cache'
import Link from 'next/link'
import { redirect } from 'next/navigation'
export default async function () {
unstable_noStore()
const session = await auth()
if (session) redirect('/')
return (
<div className="border md:border-white/10 flex items-center justify-center py-12">
<div className="mx-auto grid w-[350px] gap-6">
<div className="grid gap-2 text-center">
<h1 className="text-3xl font-bold">Sign In</h1>
<p className="text-balance text-muted-foreground">Enter your email below to sign in to your account</p>
</div>
{providers
.filter((provider) => (typeof provider === 'function' ? provider({}).id : provider.id) !== 'credentials')
.map((provider) => (
<Provider
prefix="Sign in"
key={typeof provider === 'function' ? provider({}).name : provider.name}
provider={typeof provider === 'function' ? provider({}) : provider}
/>
))}
<p className="text-gray-300 text-xs text-center">OR</p>
<form method="POST" className="grid gap-4" action="/api/auth/callback/credentials?kind=in">
<div className="grid gap-2">
<Label htmlFor="email">Email</Label>
<Input name="email" id="email" type="email" placeholder="m@example.com" required />
</div>
<div className="grid gap-2">
<Label htmlFor="password">Password</Label>
<Input name="password" id="password" type="password" required />
<CSRFInput />
</div>
<Button type="submit" className="w-full">
Sign In
</Button>
</form>
<div className="mt-4 text-center text-sm">
Don't have an account?{' '}
<Link href="/signup" className="underline">
Sign up
</Link>
</div>
</div>
</div>
)
} 上記のコードでは、サインイン ページが作成されています。ユーザーセッションがすでに存在するかどうかを確認します。その場合、ホームページにリダイレクトされます。このページには、ユーザーが電子メールとパスワードを入力できるサインイン フォームと、さまざまなプロバイダーを使用してサインインするためのオプションが表示されます。また、セキュリティのための CSRF トークンと、新規ユーザー用のサインアップ ページへのリンクも含まれています。
動的なサインアップ ページを作成する (Auth.js を使用)
page.tsx という名前のファイルを作成します。 app/signup 内 次のコードを含むディレクトリ:
// File: app/signup/page.tsx
export const runtime = 'edge'
import { CSRFInput } from '@/app/csrf'
import Provider from '@/app/provider'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { auth } from '@/lib/auth'
import providers from '@/lib/providers'
import { unstable_noStore } from 'next/cache'
import Link from 'next/link'
import { redirect } from 'next/navigation'
export default async function () {
unstable_noStore()
const session = await auth()
if (session) redirect('/')
return (
<div className="border md:border-white/10 flex items-center justify-center py-12">
<div className="mx-auto grid w-[350px] gap-6">
<div className="grid gap-2 text-center">
<h1 className="text-3xl font-bold">Sign Up</h1>
<p className="text-balance text-muted-foreground">Enter your email below to sign up with an account</p>
</div>
{providers
.filter((provider) => (typeof provider === 'function' ? provider({}).id : provider.id) !== 'credentials')
.map((provider) => (
<Provider
prefix="Sign up"
key={typeof provider === 'function' ? provider({}).name : provider.name}
provider={typeof provider === 'function' ? provider({}) : provider}
/>
))}
<p className="text-gray-300 text-xs text-center">OR</p>
<form method="POST" className="grid gap-4" action="/api/auth/callback/credentials?kind=up">
<div className="grid gap-2">
<Label htmlFor="email">Email</Label>
<Input name="email" id="email" type="email" placeholder="m@example.com" required />
</div>
<div className="grid gap-2">
<div className="flex items-center">
<Label htmlFor="password">Password</Label>
</div>
<Input name="password" id="password" type="password" required />
<CSRFInput />
</div>
<Button type="submit" className="w-full">
Sign Up
</Button>
</form>
<div className="mt-4 text-center text-sm">
Don't have an account?{' '}
<Link href="/signin" className="underline">
Sign In
</Link>
</div>
</div>
</div>
)
} 上記のコードでは、サインアップ ページが作成されています。ユーザーセッションがすでに存在するかどうかを確認します。その場合、ホームページにリダイレクトされます。このページには、ユーザーが電子メールとパスワードを入力できるサインアップ フォームと、さまざまなプロバイダーを使用してサインインするためのオプションが表示されます。また、セキュリティのための CSRF トークンと、既存のユーザー用のサインイン ページへのリンクも含まれています。
とても勉強になりました!これですべて完了です ✨
参考文献
さらに詳しい洞察については、このブログで引用されている参考文献を参照してください。
- GitHub リポジトリ
- Auth.js - 資格情報の認証
- Auth.js - Upstash Redis アダプター
- Next.js - クッキー
結論
このチュートリアルでは、Auth.js と Upstash Redis を使用して Next.js アプリケーションにユーザー認証を実装する方法を学習しました。環境変数の設定、Redis クライアントの作成、カスタム認証プロバイダーの実装について学習しました。さらに、ユーザーのサインインとサインアップのプロセス、およびセッション管理を処理するために Next.js で API ルートを設定する方法を学習しました。これらの手順に従うことで、Upstash Redis を使用して Next.js アプリケーションのユーザー認証とデータ ストレージを効果的に管理できます。
-
Vercel Edge 機能と Upstash Redis による安全で低遅延のフィルタリングでデータ漏洩を防止
データ漏洩はインターネット上の大きな問題です。 Statista によると、2022 年だけで 4 億人以上がデータ漏洩の影響を受けました。自分のデータがインターネット上に漏洩することを誰も望んでいません。そのため、ユーザーのプライバシーを尊重する安全なアプリケーションを構築することは、多くの業界で非常に重要です。 漏洩に対抗する 1 つの方法は、問題のあるデータを他のサービスやユーザーに配信する前にフィルタリングすることです。この方法では、データがすり抜けないようにするための最新のフィルタと、フィルタリングによるパフォーマンスへの影響を最小限に抑えるための低レイテンシのインフラストラクチャ
-
サーバーレスデータベース間のレイテンシーの比較:DynamoDBとFaunaDBとUpstash
この記事では、一般的なWebユースケースについて、3つのサーバーレスデータベースDynamoDB、FaunaDB、Upstash(Redis)のレイテンシーを比較します。 サンプルのニュースWebサイトを作成し、Webサイトへのリクエストごとにデータベース関連のレイテンシーを記録しています。ウェブサイトとソースコードを確認してください。 7001のNYTimesの記事を各データベースに挿入しました。記事はNewYorkTimes Archive API(2021年1月のすべての記事)から収集されます。私は各記事をランダムに採点しました。各ページリクエストで、Worldの下の上位10件の記事