如何使用 Svelte 和 GraphQL 构建全栈无服务器应用程序
AWS AI 直播!
在本教程中,你将学习如何使用Svelte.js、GraphQL 和 Fauna构建一个全栈无服务器应用程序。你将构建一个类似于Dev.to、hashnode.com或Medium 的博客平台。用户可以登录你的应用程序,创建新文章,编辑和删除自己的文章。
我们将使用以下技术栈。
- Svelte.js(Sveltekit)
- GraphQL
- 数据库动物群
- 部署(Vercel 或 Netlify)
🤖 您可以在以下GitHub链接中找到最终代码。
创建一个新的 Svelte 应用
首先,创建一个新的 Svelte 应用。在终端中运行以下命令。
npm init svelte@next blogApp
Svelte CLI 提供了一些选项来定制我们的应用程序。请选择以下选项。
✔ Which Svelte app template? › Skeleton project
✔ Use TypeScript? … No
✔ Add ESLint for code linting? Yes
✔ Add Prettier for code formatting? Yes
使用以下命令运行我们新创建的应用程序。
cd blogApp
npm i
npm run dev
在本教程中,我们将主要关注应用程序的功能,不会过多涉及样式设计。让我们开始创建一个简单的导航栏组件。创建一个新文件src/lib/Nav.svelte,并将以下代码添加到其中。
// src/lib/Nav.svelte
<nav>
<a href="/">Home</a>
<a href="/login">Login</a>
<a href="/register">Register</a>
</nav>
接下来,我们来创建一个布局文件。新建一个文件src/routes/__layout.svelte,并添加以下代码。
// src/routes/__layout.svelte
<script>
import Nav from '$lib/Nav.svelte';
</script>
<Nav />
<slot></slot>
现在运行应用程序时,Navbar每个页面都会出现一个组件。
设置 Svelte GraphQL 客户端
你的 Svelte 应用将使用 GraphQL 后端服务。有很多流行的库可用于在 Svelte 中使用 GraphQL。这个@urql/svelte库是其中最流行的库之一。接下来,我们来配置它。
运行以下命令将库添加到您的项目中。
npm i @urql/svelte --save
接下来创建一个新文件src/client.js,并添加以下代码片段。
// src/client.js
import { createClient } from '@urql/svelte';
export default createClient({
url: 'https://graphql.us.fauna.com/graphql',
// For DB in other zone use the following url
// EU: https://graphql.eu.fauna.com/graphql
// Classic: https://graphql.fauna.com/graphql
fetchOptions: () => {
const token = import.meta.env.VITE_PUBLIC_FAUNA_KEY;
return {
headers: { authorization: token ? `Bearer ${token}` : '' },
};
},
});
现在我们已经准备好从GraphQL后端查询数据了。接下来,让我们开始设置数据库。
设置数据库
如果您还没有Fauna账户,请立即创建一个。Fauna 是一个分布式无服务器数据库,它使用原生 GraphQL API。
前往动物区系控制面板并创建一个新数据库。
现在您可以定义 GraphQL schema 了。以下 ULM 图描述了如何在您的应用程序中对数据进行建模。在这个应用中,您有用户,每个用户可以发布多条帖子。这是用户与帖子has_many之间的关系。UserPost
返回代码页面,schema.graphql在根目录下创建一个新文件。添加以下代码。
# schema.graphql
type User {
username: String!
email: String!
posts: [Post!] @relation
}
type Post {
title: String!
content: String!
author: User!
}
type Query {
listPosts: [Post]
}
接下来,将模式文件上传到您的 Fauna 数据库。前往 Fauna 控制面板,选择 GraphQL 并导入模式文件。导入该schema.graphql文件。
请注意,方案上传后,您将看到一个 GraphQL Playground。您可以在此 Playground 中添加、修改和调试您的 GraphQL API。
接下来,我们向数据库添加一些数据。在 GraphQL playground 中运行以下 mutation 来创建一个新用户。
mutation CreateNewUser {
createUser(data: {
username: "shadid"
email: "shadid120@email.com"
}) {
_id
username
email
}
}
同样地,创建一个新帖子。在 GraphQL playground 中运行以下 mutation 来创建一个新帖子。
mutation CreatePost {
createPost(data: {
title: "Hello worlds"
content: "Some content"
author: {
**connect: "321522241336508481"**
}
}) {
_id
title
content
author {
email
}
}
}
请注意,我们使用了“作者 > 连接”字段。您需要在此处添加上一个 mutation 中的 userId。这将把用户与文章关联起来。因此,这篇文章的作者将是您在第一个 mutation 中创建的用户。
从 Svelte 应用查询数据
接下来,我们从 Svelte 应用中查询数据。首先,我们需要指定一个角色,并生成一个键,以便前端与数据库通信。
前往 Fauna 控制面板。选择“安全”>“角色”>“新建自定义角色”。
给你的角色命名,并授予其对集合的读取权限User。同时授予其对索引和索引的Post读取权限。post_author_by_userlistPosts
现在导航至“安全” > “钥匙” > “新建钥匙”。
为您的角色创建一个新密钥SvelteApp。
接下来,复制生成的密钥。.env在应用程序根目录下创建一个新文件,并将该密钥添加为环境变量。
# .env
VITE_PUBLIC_FAUNA_KEY=<Your Key Here>
请注意,此密钥为公钥,将对前端公开。因此,与此密钥关联的角色仅具有读取权限。
现在,让我们在首页上从数据库中导入所有文章。请将以下代码添加到您的src/routes/index.js文件中。
<script lang="js">
import { operationStore, query, setClient} from '@urql/svelte';
import client from '../client'
setClient(client);
const allPosts = operationStore(`
query GetAllPosts($size: Int!, $cursor: String) {
listPosts(_size: $size, _cursor: $cursor) {
data {
_id
title
author {
email
}
}
}
}
`,
{ size: 100 },
{ requestPolicy: 'network-only' }
);
query(allPosts);
</script>
<h1>Posts</h1>
{#if $allPosts.fetching}
<p>Loading...</p>
{:else if $allPosts.error}
<p>Oh no... {$allPosts.error.message}</p>
{:else}
{#each $allPosts.data.listPosts.data as post}
<div class="post-wrap">
<a href={`/posts/${post._id}`}>
<div>{post.title}</div>
</a>
<span>by {post.author.email}</span>
</div>
{/each}
{/if}
<style>
.post-wrap {
margin-bottom: 1rem;
}
</style>
重启应用程序。你会发现现在所有帖子都位于应用程序的根 URL 下。
请注意,当您选择帖子时,应用会将您带到/post/:id路线规划页面。您可以在该路线规划页面中查看各个帖子。接下来,我们来创建这条路线规划页面。
创建一个新文件routes/posts/[id].svelte,并添加以下代码。
// routes/posts/[id].svelte
<script lang="js">
import { operationStore, query, setClient} from '@urql/svelte';
import { page } from '$app/stores';
import client from '../../client'
setClient(client);
const currentPost = operationStore(`
query GetPostById($id: ID!) {
findPostByID(id: $id) {
_id
title
content
author {
email
}
}
}
`,
{ id: $page.params.id }
)
query(currentPost)
</script>
{#if $currentPost.fetching}
<p>Loading...</p>
{:else}
<h2>{$currentPost.data.findPostByID.title}</h2>
<p>By <b>{currentPost.data.findPostByID.author.email}</b></p>
<p>{$currentPost.data.findPostByID.content}</p>
{/if}
身份验证和授权
接下来,我们来为应用程序添加身份验证功能。我们可以使用fauna-gql-upload`and`fauna-graphql-tool库轻松添加身份验证。首先,让我们将这些依赖项添加到项目中。
npm i @fauna-labs/graphql-tool fauna-gql-upload --save-dev
这些库是自动化脚本,您需要从 Fauna 获取管理员密钥才能运行这些工具。
前往动物区系控制面板。
选择“安全”>“钥匙”>“新建钥匙”。
创建一个新的管理员密钥。确保角色设置为管理员。
请勿与任何人共享此管理员密钥,也不要将其与您的应用程序一起部署。管理员密钥只能与自动化/迁移工具一起使用。
将管理员密钥添加到.env变量中。确保您的.env文件已添加到 .gitignore 列表中。
##.env
VITE_PUBLIC_FAUNA_KEY=<Fauna Public Key>
FGU_SECRET=<Your Admin Key>
接下来,您需要对 GraphQL schema 进行以下更改。
type User **@auth(primary: "email")** {
username: String!
email: String!
posts: [Post!] @relation
}
type Post **@protected(membership: "User", rule: ["read", "write", "create"])** {
title: String!
content: String!
author: User!
}
type Query {
listPosts: [Post]
}
请注意,在之前的代码块中,我们@auth向 User 集合添加了一个指令。这意味着我们将使用 User 集合进行身份验证。该primary指令定义了用于注册和登录用户的字段。在本例中,该指令为 `<your_email_name>` email。因此,用户可以使用其电子邮件地址和密码登录。
请注意,@protectedPost 集合* 中添加了一条指令。 * 此指令定义了访问模式。已登录用户可以撰写和创建新帖子。
将这些更改添加到架构后,打开该package.json文件,并在脚本部分添加以下代码片段。
// package.json
{
...
"script": {
...
"fgu": "fgu",
"fgt": "fgt"
}
}
我们在这里添加这些脚本,以便我们可以从 npm 运行fauna-graphql-tool(fgt) 和fauna-gql-upload(fgu)。
fgt它会获取您的 GraphQL schema,并将 schema 编译成各种数据库资源(例如集合、用户定义的函数、身份验证规则),然后fgu将这些资源上传到 Fauna。
最后,在终端中运行以下命令。
npm run fgt && npm run fgu
/fauna请注意,系统会生成一个包含所有资源的新文件夹。
-
📗 专业提示:
请注意,运行脚本后会创建一个名为 `<path>` 的新文件夹
/fauna。您可以打开此文件夹,查看自动化脚本创建的各种功能和角色。如果您想进一步自定义身份验证规则,请随时在此处修改逻辑。
如果您想了解这些资源的工作原理,请查阅Fauna JavaScript 驱动程序的相关文档。
现在,当你回到 Fauna 中的 GraphQL playground 时,你会注意到mutation 对你register来说是可用的。login
最后,前往“安全”>“角色”>“SvelteRole”,并将您的角色调用权限授予这些新生成的函数。user_by_email同时,请确保授予对索引的读取权限,因为登录函数会使用此索引。
用户注册表单
接下来,我们来创建用户注册表单。创建一个新文件src/routes/register.svelte并添加以下代码。
// src/routes/register.svelte
<script lang="js">
import { setClient, mutation } from '@urql/svelte';
import client from '../client'
import { goto } from '$app/navigation';
setClient(client);
const registerMutation = mutation({
query: `
mutation ($email: String!, $password: String!) {
register(email: $email, password: $password) {
email
_id
}
}
`,
});
async function onSubmit(e) {
const formData = new FormData(e.target);
const data = {};
for (let field of formData) {
const [key, value] = field;
data[key] = value;
}
const { email, password } = data;
const resp = await registerMutation({ email, password })
if (resp.data.register) {
goto('/');
}
if(resp.error) {
alert(resp.error.message);
console.log(resp.error);
}
}
</script>
<div class="wrap">
<h3>Register New User</h3>
<form on:submit|preventDefault={onSubmit}>
<div>
<label for="name">Email</label>
<input
type="text"
id="email"
name="email"
value=""
/>
</div>
<div>
<label for="name">Password</label>
<input
type="password"
id="password"
name="password"
value=""
/>
</div>
<button class="button is-light" type="submit">Register</button>
</form>
</div>
前面的代码块中有一个简单的表单组件。表单提交后,register会执行 mutation 操作,并注册一个新用户。
用户登录表单
接下来,我们来创建一个用户登录表单。我们可以将用户会话保存在浏览器 cookie 中。这个js-cookie库可以轻松实现这一点。在终端中运行以下命令即可添加此库。
npm i js-cookie --save
创建一个新文件src/routes/login.svelte,并添加以下代码。
<script>
import { setClient, mutation } from '@urql/svelte';
import client from '../client';
import Cookies from 'js-cookie';
import { goto } from '$app/navigation';
setClient(client);
const loginMutation = mutation({
query: `
mutation ($email: String!, $password: String!) {
login(email: $email, password: $password) {
secret
ttl
data {
_id
email
}
}
}
`,
});
async function onSubmit(e) {
const formData = new FormData(e.target);
const data = {};
for (let field of formData) {
const [key, value] = field;
data[key] = value;
}
const { email, password } = data;
const resp = await loginMutation({ email, password })
if(resp.data.login.data) {
Cookies.set(
'MY_BLOG_APP_TOKEN',
JSON.stringify({
id: resp.data.login.data._id,
secret: resp.data.login.secret
}),
{ expires: resp.data.login.data.ttl }
);
alert('Login Successful');
goto('/')
}
}
</script>
<div>
<h3>Login Form</h3>
<form on:submit|preventDefault={onSubmit} >
<div>
<label for="name">Email</label>
<input
type="text"
id="email"
name="email"
value=""
/>
</div>
<div>
<label for="name">Password</label>
<input
type="password"
id="password"
name="password"
value=""
/>
</div>
<button type="submit">Submit</button>
</form>
</div>
前面的代码块中有一个简单的表单组件。表单提交后,login会触发 mutation。登录成功后,Fauna 会返回一个新的 token。这个 token 是经过身份验证的用户 token。我们会js-cookie将此 token 存储在浏览器 cookie 中。
创建新帖子
在我们的应用程序中,已登录用户可以创建新帖子。请clientWithAuthToken在您的client.js文件中创建一个名为 `createPosts` 的新函数。您可以传入从会话 cookie 中获取的身份验证令牌,该函数将使用该会话令牌设置 GraphQL 客户端。
// src/client.js
export const clientWithAuthToken = token => createClient({
url: 'https://graphql.us.fauna.com/graphql',
fetchOptions: () => {
console.log('token', token);
return {
headers: { authorization: token ? `Bearer ${token}` : '' },
};
},
});
接下来,我们来创建一个用户可以发布新帖子的页面。
创建一个新文件src/routes/posts/new.svelte,并将以下代码添加到该文件中。
// src/routes/posts/new.svelte
<script lang="js">
import Cookies from 'js-cookie';
import { setClient, mutation } from '@urql/svelte';
import { clientWithAuthToken } from '../../client';
import { goto } from '$app/navigation';
let userSession = Cookies.get('MY_BLOG_APP_TOKEN');
let authorId;
if(userSession) {
const { secret, id } = JSON.parse(userSession);
authorId = id;
setClient(clientWithAuthToken(secret));
}
const newPost = mutation({
query: `
mutation CreatePost($title: String!, $content: String! $authorId: ID!) {
createPost(data: {
title: $title
content: $content
author: {
connect: $authorId
}
}) {
_id
title
content
}
}
`,
});
async function onSubmit(e) {
const formData = new FormData(e.target);
const data = {};
for (let field of formData) {
const [key, value] = field;
data[key] = value;
}
const { content, title } = data;
try {
console.log('authorId', authorId);
if(!authorId) {
alert('You must be logged in to create a post');
return;
}
const resp = await newPost({ title, content, authorId });
if(resp.data.createPost) {
alert('Post created successfully')
goto('/')
}
} catch (error) {
console.log(error);
}
}
</script>
<div>
<h3>New Post</h3>
{#if !userSession}
<p class="login-promt">You must be logged in to create a post</p>
{/if}
<form on:submit|preventDefault={onSubmit} >
<div class="input-blocks">
<label for="name">Title</label>
<input
type="text"
name="title"
value=""
/>
</div>
<div class="input-blocks">
<label for="name">Content</label>
<textarea
type="text"
name="content"
value=""
/>
</div>
<button type="submit">Submit</button>
</form>
</div>
<style>
.input-blocks {
display: flex;
flex-direction: column;
max-width: 300px;
margin-bottom: 1em;
}
.login-promt {
color: coral;
}
</style>
在之前的代码块中,当用户提交表单时,createPostmutation 会触发。请注意,我们使用会话令牌clientWithAuthToken来设置 GraphQL 客户端。您可以从浏览器 cookie 中获取会话令牌,并使用它来设置 GraphQL 客户端。如果用户未登录或会话令牌已过期,则此 mutation 将无法正常工作。
删除帖子
让我们添加删除帖子的功能。创建一个新组件src/lib/Delete.svelte
并添加以下代码。
// src/lib/Delete.svelte
<script lang="js">
import Cookies from 'js-cookie';
import { clientWithAuthToken } from '../client';
import { setClient, mutation } from '@urql/svelte';
import { page } from '$app/stores';
import { goto } from '$app/navigation';
let userSession = Cookies.get('MY_BLOG_APP_TOKEN');
if (userSession) {
setClient(clientWithAuthToken(userSession))
const {secret } = JSON.parse(userSession);
setClient(clientWithAuthToken(secret));
}
const deletePost = mutation({
query: `
mutation DeletePost($id: ID!) {
deletePost(id: $id) {
_id
title
}
}
`
})
async function handleDelete() {
const { data, error } = await deletePost({ id: $page.params.id });
if(error) {
console.log('error', error);
alert('error', error.message);
return;
}
if(data.deletePost) {
alert('Post deleted');
goto('/')
}
}
</script>
<button on:click|preventDefault={handleDelete} disabled={!userSession}>Delete</button>
此组件渲染一个按钮。当按钮被选中时,它会deletePost使用已认证用户的令牌触发 mutation。
将此组件添加到您的src/routes/posts/[id].svelte页面。
<script lang="js">
...
</script>
...
<Delete />
{/if}
但是请注意,当您点击该按钮时,会收到“权限被拒绝”的提示信息。这是因为我们尚未设置删除权限。
再次前往 Fauna 控制面板,然后选择“安全”>“角色”>“用户角色”。
在Post收藏夹中选中删除选项并选择保存。
🤔 如果您只想让帖子所有者删除帖子该怎么办?添加此规则非常简单。从帖子下拉菜单中选择删除规则即可。
在谓词规则中添加以下代码片段。该谓词规则定义只有帖子作者才能删除帖子。
Lambda("ref", Equals(
Identity(), // logged in user
Select(["data", "author"], Get(Var("ref")))
))
编辑帖子
接下来,我们来添加文章编辑功能。请创建一个新组件/src/lib/Edit.svelte并添加以下代码。
// /src/lib/Edit.svelte
<script lang="js">
import { operationStore, query, setClient } from '@urql/svelte';
import { page } from '$app/stores';
import client from '../../client'
import Delete from '$lib/Delete.svelte';
import Edit from '$lib/Edit.svelte';
setClient(client);
const currentPost = operationStore(`
query GetPostById($id: ID!) {
findPostByID(id: $id) {
_id
title
content
author {
email
}
}
}
`,
{ id: $page.params.id },
{ requestPolicy: 'network-only' }
)
query(currentPost)
export let post = null;
currentPost.subscribe(({data}) => {
if(data) {
post = data.findPostByID;
}
})
</script>
{#if $currentPost.fetching}
<p>Loading...</p>
{:else}
<h2>{$currentPost.data.findPostByID.title}</h2>
<p>By <b>{currentPost.data.findPostByID.author.email}</b></p>
<p>{$currentPost.data.findPostByID.content}</p>
<Edit post={post}/>
<Delete />
{/if}
此组件是一个基础表单组件,其中的数据已预先填充posts/[id].svelte。表单提交时,此组件会触发编辑帖子变更。
将此组件添加到您的src/routes/posts/[id].svelte文件中。
<script lang="js">
import Edit from '$lib/Edit.svelte';
...
export let post = null;
currentPost.subscribe(({data}) => {
if(data) {
post = data.findPostByID;
}
})
</script>
...
<Edit post={post}/>
{/if}
修改完成后,文件中的代码src/routes/posts/[id].svelte应如下所示。
// src/routes/posts/[id].svelte
<script lang="js">
import { operationStore, query, setClient } from '@urql/svelte';
import { page } from '$app/stores';
import client from '../../client'
import Delete from '$lib/Delete.svelte';
import Edit from '$lib/Edit.svelte';
setClient(client);
const currentPost = operationStore(`
query GetPostById($id: ID!) {
findPostByID(id: $id) {
_id
title
content
author {
email
}
}
}
`,
{ id: $page.params.id },
{ requestPolicy: 'network-only' }
)
query(currentPost)
export let post = null;
currentPost.subscribe(({data}) => {
if(data) {
post = data.findPostByID;
}
})
</script>
{#if $currentPost.fetching}
<p>Loading...</p>
{:else}
<h2>{$currentPost.data.findPostByID.title}</h2>
<p>By <b>{currentPost.data.findPostByID.author.email}</b></p>
<p>{$currentPost.data.findPostByID.content}</p>
<Edit post={post}/>
<Delete />
{/if}
更新模板以反映用户身份验证状态
目前,我们的应用模板在用户登录状态下不会改变。让我们来改变这一点。
创建一个新文件src/store.js。在该文件中创建一个新的可写存储区,用于保存用户会话数据。将以下代码添加到该文件中。
import { writable } from 'svelte/store';
export const userSession = writable(null);
接下来,每当用户登录时,将用户信息写入此存储。请对文件进行以下代码更改src/routes/login.svelte。
<script>
...
import { userSession } from '../store';
...
async function onSubmit(e) {
...
if(resp.data.login.data) {
...
userSession.update(() => ({
email,
id: resp.data.login.data._id,
secret: resp.data.login.secret
}));
alert('Login Successful');
goto('/')
}
}
</script>
最后,使用以下代码更新src/lib/Nav.svelte文件。以下代码块用于监听应用商店的任何更改。如果用户已登录,则应用会显示注销表单;否则,会显示登录和注册链接。
<script lang="js">
import { userSession } from '../store.js';
import Cookies from 'js-cookie';
let user;
userSession.subscribe(val => {
user = val;
});
function logout() {
userSession.update(() => null);
Cookies.remove('MY_BLOG_APP_TOKEN');
}
</script>
<nav>
<a href="/">Home</a>
{#if user}
<!-- svelte-ignore a11y-invalid-attribute -->
<a href="#" on:click={logout}>Logout</a>
{:else}
<a href="/login">Login</a>
<a href="/register">Register</a>
{/if}
<hr />
</nav>
部署
维塞尔
现在,我们的应用程序已准备就绪,可以上线了。您可以使用 Vercel 轻松部署 Svelte 应用程序。如果您还没有 Vercel 帐户,请创建一个新帐户。接下来,运行以下命令并按照说明操作。
npx vercel --prod
Netlify
请按照以下文章中的步骤进行 Netlify 部署。
https://dev.to/danawoodman/deploying-a-sveltekit-app-to-netlify-5dc3
好了,就到这里。希望这篇文章对您有所帮助,让您对使用 Svelte 和 GraphQL 开发全栈无服务器应用程序有了全面的了解。如果您有任何反馈,欢迎在评论区留言。如果您有任何问题,欢迎随时通过我的 Twitter @HaqueShadid 联系我。
文章来源:https://dev.to/shadid12/how-to-build-a-full-stack-serverless-application-with-svelte-graphql-and-fauna-5427





















