Faunadb是具有内置文档数据模型的多范式关系数据库。 Faunadb为开发人员提供了使用云API中的关系和文档数据库的好处,该数据库几乎可以从任何平台访问。 Faunadb的查询语言称为FQL(Fauna查询语言),一种面向表达的查询语言,可以实现复杂,精确的操作和对文档的检索。所有FQL查询都是交易确保数据完整性的。 Faunadb是多区域的,并使用主动活动群集水平扩展,同时合并大多数现代的应用程序安全功能。
在今天的帖子中,我们将浏览以下内容,而我们发现了什么是faunadb;
- 它的工作原理
- 创建一个帐户和数据库
- 设置本地开发
- 运行FQL查询
Faunadb如何工作?
文件
faunadb将您的数据存储为类似JSON的对象。这种方法类似于您在MongoDB,Firestore和其他面向文档的数据库中观察到的方法。所有文档都有时间戳和独特的参考。文档ref由集合的名称和字符串中的数字组成。默认情况下,所有文档都是不变的,并且更新创建新文档,每个文档都允许其时间戳进行延时查询。
收藏
收集只是将相关文档分组的一种方式,它们与SQL中的表相似。可以将多个示意性文档存储在集合中,当删除集合时,相关的文档变得无法访问并被异步删除。
参考
参考是一个复合值,它是集合中任何文档的唯一ID,参考由对包含文档的集合的引用和数字ID组成。数据库中的两个文档可以共享相同的参考。
索引
索引允许通过数据属性进行组织和轻松检索文档。索引只是一个查找表,可以加快查找文档的速度,您可以查询一个索引以查找内部的文档,而不是读取集合中的所有文档以选择几个文档。
。功能
FQL提供可用于运行查询的内置功能。我们可以组合多个内置功能来创建用户定义的功能(或UDFS)UDFS用于将常用的函数组合到可以重复存储和执行的查询中。
>创建一个帐户
要创建一个Faunadb帐户,您需要前往Fauna website。免费单击“开始”,然后选择GitHub作为您的身份提供者,然后从Github授权Faunadb。如果一切顺利,应该为您成功创建您的帐户,并且您应该重定向到您的Faunadb仪表板。
设置数据库
现在,您已经成功创建了一个帐户,在仪表板上单击创建数据库按钮,接下来我们需要给数据库一个名称,为您的数据选择一个区域,然后选择使用demo-data
来确保创建数据库为我们提供一些演示数据。继续单击创建,如果一切按计划进行。
项目设置
在您喜欢的编辑器中打开一个空文件夹,让我们使用FaunadB设置Nodejs应用程序。运行以下命令以旋转一个新的节点项目
$ npm init --y
接下来,我们需要安装一些基本的扩展名来帮助启动服务器,
首先,让我们安装Express以帮助为我们创建基本服务器。
$ npm i express
运行以下命令安装faunadb和dotenv扩展。
$ npm i faunadb dotenv
现在,我们已经成功安装了应用程序依赖项,返回您的Faunadb仪表板,并确保您单击新创建的数据库。单击“安全性”选项卡,然后单击创建新的数据库键。给密钥一个名称,并确保您设置了键为服务器并继续进行的角色。如果一切都按计划进行,则应看到为您生成的API键,请复制此键并将其存储在项目目录中的DotEnv文件中。
运行FQL查询
创建一个名称app.js
的新JavaScript文件。我们将编写和导出该模块中与FaunAdb交互的功能。我们需要做的第一件事是创建一个集合,所以让我们为此编写逻辑。
const { query, Client } = require('faunadb');
require('dotenv').config();
const client = new Client({
secret: process.env.SECRETE_KEY,
});
//Create a collection
const createCollection = async (name) => {
try {
const collection = await client.query(
query.CreateCollection({ name })
);
return [null, collection]
} catch (error) {
return [error, collection]
}
}
module.exports = {
createCollection,
}
我们与Client
一起导入了query
对象。 query
对象包含我们与FAUNADB数据库进行交互所需的方法。然后,我们创建了Client
的新实例,我们需要使用之前创建的秘密密钥对客户端进行身份验证。现在,我们有一个客户,让我们继续进行查询。我们有一个函数createCollection
。此功能接受一个单个参数name
,该参数将用作我们正在创建的集合的名称。有一个try/catch
块来结束创建集合并优雅地处理我们错误的过程。这将是我们将创建其他功能的模式。在try
块中,我们称之为client.query
,这就是您使用FQL进行查询的方式。
此功能接受查询作为其参数。要创建一个集合,我们将query.CreateCollection
调用并传递给具有名称属性的对象作为参数。我们存储查询的结果,该查询是变量collection
中的新创建的集合。我们返回以NULL为第一个值的数组,将collection
作为第二个值返回。这也将是此代码库中的模式,所有辅助功能都必须返回一个数组对象,其中一个值代表错误对象,第二个值是从函数中的返回值。
在catch
块内部,我们也返回一个数组,匹配从辅助功能返回数组的模式,这一次是在第二个参数为null时抛出的错误对象。然后,我们使用module.export
导出createCollection
函数。现在,让我们在新文件index.js
中创建基本的Express服务器
// index.js
const express = require('express');
const { createCollection } = require('./app');
const app = express();
app.use(express.json());
app.post('/collection', async (req, res) => {
const {name} = req.body;
try {
const [error, collection] = await createCollection(name);
if (error) throw error
if (collection) res.status(200).json(collection)
} catch(error) {
return res.status(400).json(error)
}
})
app.listen(3000, () => console.log('App running on PORT 3000'));
在我们导入的index.js
文件中,然后我们从app.js
导入createCollection
函数。我们创建一个新的Express应用程序,并将Express上的JSON解析器用作应用中间件。接下来,我们注册一条路线和一个邮政请求的处理程序。该路线是/collection
,在处理程序功能内部,我们从请求正文中破坏了名称,在try
块中,我们称之为等待createCollection
并以名称传递。我们破坏error
和collection
。如果有error
,那么如果成功创建了集合,我们只会丢弃错误,我们将其发送回响应。
在catch
块中,我们刚刚设置了对400
的响应状态,然后我们将错误发送回响应,然后在端口3000上侦听连接。如果您使用Nodemon运行此应用;
$ nodemon i index.js
如果一切顺利,您应该在控制台中看到App running on PORT 3000
的消息。让我们对端点进行查询以测试它。
POST http://localhost:3000/collection
Content-Type: application/json
{
"name": "todos"
}
如果您提出此请求,则应该得到看起来像这样的响应;
{
"ref": {
"@ref": {
"id": "todo",
"collection": {
"@ref": {
"id": "collections"
}
}
}
},
"ts": 1691589603965000,
"history_days": 0,
"name": "todo"
}
现在,我们有一个集合,让我们继续添加一些数据。
// app.js continued
const createTodo = async (note) => {
try {
const todo = await client.query(
query.Create(
query.Collection('Todos'),
{ data: { status: 'PENDING', note } }
)
);
return [null, todo];
} catch (error) {
return [error, null];
}
}
module.exports = {
createCollection,
createTodo
}
上面的摘要处理创建新的todo对象的过程。我们有一个函数createTodo
,它接受字符串note
作为其参数。在try
块中,我们称为client.query
并传递查询。要创建一个新文档,您需要调用query.Create
,然后将REF传递给您希望该文档所属的集合。 Create
方法接受的第二个参数是具有data
属性的数据对象。此data
属性的值是我们要存储在文档中的数据。我们的数据将具有status
属性,该属性将为PENDING
和Note属性,该属性将作为Todo的标题。让我们为其设置处理程序。
//index.js continued
const {
createCollection,
createTodo,
} = require("/.app.js");
app.post('/todo', async (req, res) => {
const {note} = req.body;
try {
const [error, todo] = await createTodo(note);
if (error) throw error
if (todo) res.status(200).json(todo)
} catch(error) {
return res.status(400).json(error)
}
})
我们在应用程序上注册了一个备用后处理程序。该路线是/todo
,在处理程序内部,我们会破坏请求主体的注释。在try
块中,我们创建一个新的todo,并用响应将其发送回响应,否则如果有错误,我们以400的状态将该错误发送回该错误。现在让我们测试此端点。
POST http://localhost:3000/todo
Content-Type: application/json
{
"note": "Cleaning"
}
服务器的响应应该看起来像这样;
{
"ref": {
"@ref": {
"id": "372695699207225424",
"collection": {
"@ref": {
"id": "todos",
"collection": {
"@ref": {
"id": "collections"
}
}
}
}
}
},
"ts": 1691689166285000,
"data": {
"status": "PENDING",
"note": "Cycling"
}
}
继续创建更多的娱乐活动,现在我们需要设置一个索引。索引是FQL的核心,它们将是我们将如何从FQL检索数据的方式,让我们设置一个索引来检索所有已待处理的Todos。
// app.js continued
// CREATE INDEX
const createIndex = async (
name,
collection,
field
) => {
try {
let index;
if (field) {
index = await client.query(
query.CreateIndex({
name,
source: query.Collection(collection),
terms: [{ field }],
})
);
} else {
index = await client.query(
query.CreateIndex({
name,
source: query.Collection(collection),
})
);
}
return [null, index];
} catch (error) {
console.log(error)
return [error, null];
}
}
module.exports = {
createCollection,
createTodo,
createIndex,
}
首先,我们定义createIndex
函数。该功能需要三个参数,并返回一个带有两个元素的数组。在尝试块内部,我们声明了一个称为index
的变量。该变量将存储创建的索引。然后,我们检查字段参数是否未定义。如果未定义,该函数将在指定字段上创建索引。否则,该功能会在整个集合上创建索引。我们使用client.query()
方法来创建索引。 query.CreateIndex()
方法创建了一个新索引。名称参数指定索引的名称。源参数指定将创建索引的集合。术语参数指定将创建索引的字段。然后,我们将功能从此模块中导出。让我们为此设置一个处理程序。
// index.js continued
const {
createIndex
} = require('./app');
app.post('/index', async (req, res) => {
const {name,collection,field} = req.body;
try {
const [error, index] = await createIndex(
name,
collection,
field ?? null
);
if (error) throw error
if (index) res.status(200).json(index)
} catch(error) {
return res.status(400).json(error)
}
});
我已将处理程序函数附加到/index
路由上,该路由将在向该路线提出后请求时执行。路由处理程序功能首先解析请求主体并提取name
,collection
和field
参数。名称参数指定要创建的索引的名称。集合参数指定将创建索引的集合的名称。字段参数指定将创建索引的字段。如果省略此参数,则将在整个集合上创建索引。
然后该功能调用createIndex
函数以创建索引。 createIndex
函数返回一个带有两个元素的数组:第一个元素是错误消息,第二个元素是索引。如果CreateIndex函数返回错误消息,则处理程序功能会引发错误。该错误将作为400不良请求响应发送回客户端。如果CreateIndex函数返回索引,则处理程序函数将索引作为200 OK响应发送给客户端。让我们测试这个端点。
POST http://localhost:3000/index
Content-Type: application/json
{
"name": "todos_by_status",
"collection": "todos",
"field": ["data", "status"]
}
响应应该看起来像这样;
{
"ref": {
"@ref": {
"id": "todos_by_status",
"collection": {
"@ref": {
"id": "indexes"
}
}
}
},
"ts": 1691691134550000,
"active": true,
"serialized": true,
"name": "todos_by_status",
"source": {
"@ref": {
"id": "todos",
"collection": {
"@ref": {
"id": "collections"
}
}
}
},
"partitions": 8
}
现在,让我们设置一个函数以检索所有待处理的戒酒;
// app.js continued
const getTodoByIndex = async (
index,
value
) => {
try {
const todos = await client.query(
query.Map(
query.Paginate(
query.Match(
query.Index(index),
value
)
),
query.Lambda(x => query.Get(x))
)
);
return [null, todos]
} catch (error) {
return [error, null]
}
}
module.exports = {
createCollection,
createTodo,
createIndex,
getTodoByIndex,
}
上方的摘要首先定义了一个称为getTodoByIndex
的函数。该函数采用两个参数:index
参数指定索引的名称,而value
参数指定了将索引与匹配的值。然后,该函数使用client.query()
方法执行查询。客户端变量是对Faunadb客户端的引用。 query.Map()
方法创建了一个新地图。 query.Paginate()
方法在查询结果。
query.Match()
方法与匹配指定索引和值匹配的集合中的文档匹配。 query.Lambda()
方法创建了lambda函数。 lambda函数将针对查询返回的每个文档执行。 query.Get()
方法从数据库获取文档。该函数返回一个带有两个元素的数组:第一个元素是错误消息,第二个元素是查询返回的文档数组。让我们设置一个处理程序功能以检索待处理的待办事项。
// index.js continued
const {
getTodoByIndex
} = require("./app");
app.get('/todos/index', async (req, res) => {
const {index, value} = req.body
try {
const [error, todo] = await getTodoByIndex(
index,
value
);
if (error) throw error
if (todo) res.status(200).json(todo)
} catch(error) {
return res.status(400).json(error)
}
})
我们已将路由处理程序功能附加到/todos/index
路由上,该路由将在向该路线提出Get请求时执行。功能首先解析请求主体并提取index
和value
参数。 index
参数指定索引的名称,而value
参数指定索引与匹配的值。然后,该函数调用getTodoByIndex
函数以检索戒酒。
如果GetTodobyIndex函数返回错误消息,则处理程序功能会引发错误。该错误将作为400不良请求响应发送回客户端。如果getTodobyIndex函数返回文档数组,则处理程序功能将文档的数组发送回客户端,作为200 OK响应。让我们测试这个端点。
GET http://localhost:3000/todos/index
Content-Type: application/json
{
"index": "todos_by_status",
"value": "PENDING"
}
我们应该恢复一系列的招待,让我们创建一个处理程序功能以检索单个todo;
// app.js continued
const getTodo = async (id) => {
try {
const todo = await client.query(query.Get(
query.Ref(
query.Collection("todos"),
id
)
));
return [null, todo];
} catch (error) {
return [error, null];
}
}
module.exports = {
createCollection,
createTodo,
createIndex,
getTodoByIndex,
getTodo,
}
我们定义了一个称为getTodo
的函数,该函数从Todos Collection中获取todo
文档。该函数采用一个参数:指定要获取文档的ID的id
参数。该函数首先使用client.query()
方法执行查询。 query.Get()
方法从数据库获取文档。 query.Ref()
方法创建了对文档的引用。
query.Collection()
方法指定文档所在的集合。id
参数指定文档的ID。该函数返回一个带有两个元素的数组:第一个元素是错误消息,第二个元素是查询返回的文档。让我们设置一个处理程序功能以检索todo
// index.js continued
const {getTodo} = require('./app');
app.get('/todo/:id', async (req, res) => {
const {id} = req.params;
try {
const [error, todo] = await getTodo(id);
if (error) throw error
if (todo) res.status(200).json(todo)
} catch(error) {
return res.status(400).json(error)
}
})
我们有一个路由处理程序功能,该功能从ID的Todos Collection中获取todo文档。该函数已连接到/todo/:id
路由,因此在向该路线提出GET请求时将执行它。功能首先解析请求参数并提取ID参数。 id
参数指定要获取的文档的ID。然后,该功能调用getTodo
函数以获取todo。如果GetToDo函数返回错误消息,则处理程序功能会引发错误。该错误将作为400不良请求响应发送回客户端。如果GetTodo函数返回文档,则处理程序功能将文档发送给客户端以200个OK响应。最后,让我们设置一个辅助功能以更新todo。
// * UPDATE TODO
const updateTodo = async (id, status) => {
try {
const todo = await client.query(
query.Update(
query.Ref(query.Collection("Todos"), id),
{ data: { status: status } },
)
)
return [null, todo];
} catch (error) {
return [error, null]
}
}
module.exports = {
createCollection,
createTodo,
createIndex,
getTodo,
getTodoByIndex,
updateTodo
}
我创建了一个名为updateTodo
的函数,该函数在Todos Collection中更新了TODO文档。该函数采用两个参数:id
参数指定要更新文档的ID,并且状态参数指定文档的新状态。 query.Update()
方法更新文档。 query.Ref()
方法创建了对文档的引用。 query.Collection()
方法指定了该文档所在的集合。id
参数指定文档的ID。数据对象指定将在文档中更新的数据。在这种情况下,正在更新状态属性。该函数返回一个带有两个元素的数组:第一个元素是错误消息,第二个元素是已更新的文档。让我们为此设置一个路由处理程序函数;
// index.js continued
const {updateTodos} = require('./app');
app.patch('/todo/:id', async (req, res) => {
const {id} = req.params;
const {status} = req.body;
try {
const [error, todo] = await updateTodo(id, status);
if (error) throw error
if (todo) res.status(200).json(todo)
} catch(error) {
return res.status(400).json(error)
}
})
我们有一个创建的路由处理程序功能,可更新Todos集合中的TODO文档的状态。该功能连接到/todo/:id
路由,因此在对该路由的补丁请求时将执行。功能首先解析请求参数并提取ID和状态参数。 id
参数指定要更新文档的ID,并且状态参数指定文档的新状态。然后,该功能调用updateTodo
函数以更新todo。
如果updateTodo
函数返回错误消息,则处理程序功能会引发错误。该错误将作为400
不良请求响应发送回客户端。如果updateTodo函数返回文档,则处理程序函数将文档发送回客户端以200
ok响应。
我将在这里停下来,让您弄清楚如何检索集合中的所有文档以及如何从集合中删除文档。由于时间因素,FaunadB具有GraphQL API,我们跳过了很多东西,并且可以自动创建一个GraphQl端点以与我们的数据进行交互。我们不仅限于在后端使用FaunAdb,还可以在React或Svelte Project等前端应用中使用它。我们只是在FQL中几乎没有触摸Lambda功能,我们只是刷了FQL的表面,这些概念中的每一个都需要单独的帖子。在下面的评论部分中留下您的想法,有关Faunadb,FQL和我们应该介绍的下一个概念。