使用Prisma的动态过滤
#javascript #typescript #prisma #backend

如果您已经使用了Prisma,那么您已经知道了所有的惊人,除了您在大型团队中工作时的迁移或模式部分。

但是,无论如何,今天我的博客将是关于我们如何使用Prisma查询来实现动态过滤!

为您提供更多的上下文,我所说的是构建一个端点,完全可自定义:

  • 通过任何列搜索
  • 通过任何列订购 而且,是的,多个过滤器子句!

这怎么可能?

也许你们中的一些人已经有点猜怎么着,这是因为,Prisma使用JS / TS最珍贵的东西(对象)来执行任何查询,尤其是findMany查询。


const blogs = await prisma.blogs.findMany({
    where:{
       title: "A blog"
    },
    orderBy:{
        createdAt:'desc'
    }
})

请参阅基本上是由对象形成的WHEREclause和ORDER BY

这就是我们进行的尝试。
在本第1部分中,我们将使用WHERE子句进行数据过滤,其他情况希望在本系列的下一部分中介绍! :D

足够的谈话,让我们开始

配置

首先,让我们只用几个依赖项设置我们的迷你服务器

npm init -y

NPM安装Express Prisma @prisma/client

也不要忘记初始化Prisma文件夹!

npx prisma sun

好吧,现在我们在项目中基本上有类似的东西

  • node_modules
  • Prisma
    • schema.prism

如果您不想遇到设置的麻烦,则可以克隆存储库here

数据库配置和Prisma模式

如果您有点新,Prisma架构还包含数据库URL,当您初始化Prisma Client实例时,实际上可以在代码上对其进行覆盖。

您要做的就是在.env文件中填写数据库连接URI(从npx prisma init生成),然后选择您的提供商,我将使用mysql进行本教程。但是我认为Postgres也没有区别!

对于本教程,我们将保持简单,我们将与两个实体一起做

  • 博客
  • 用户

,我将仅将枚举用于博客类别。

模式看起来像这样:

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

enum BlogCategory {
  BACKEND
  FRONTEND
  MOBILE
}

model Blogs {
  id        String       @id @default(uuid())
  title     String
  content   String
  category  BlogCategory
  writer    User         @relation(fields: [writerId], references: [id])
  writerId  String
  createdAt DateTime     @default(now())
  updatedAt DateTime     @updatedAt
}

model User {
  id       String  @id @default(uuid())
  username String  @unique
  fullName String
  blogs    Blogs[]
}


我们已经完成了模式,现在我们只需要使用此命令迁移它:

npx prisma迁移dev -name migrationName

执行

文件夹结构

基本上,我只会将代码分为某些部分

  • server.js(我们基于明确的网络服务器进行请求和响应)

  • 服务/blogservice.js(我们在这里进行逻辑)

  • services/buildfilterquery.js(在此处将其传递给查询之前,我们将应用过滤)

  • prisma-instance.js(Prisma实例将在此处存储)

基本上看起来像这样:

Image description

唯一的服务器和Prisma实例

在这一部分,我们将创建一条路线,然后在其中,我们将检查提供的任何过滤器。

server.js


const express = require("express");
const { getAll } = require("./blogs/blogService");


const app = express();



const checkFilteringQuery = (req) => {
    const enabledFilters = {};

    if (req.query.searchKey) {
        enabledFilters.searchKey = req.query.searchKey;
    }

    if (req.query.searchValue) {
        enabledFilters.searchValue = req.query.searchValue;
    }

    if (req.query.filters) {
        // This needs to be parsed into object first, because we are retrieving this from a request query, which is basically just a string.
        enabledFilters.filters = JSON.parse(req.query.filters);
    }


    if (req.query.orderKey) {
        enabledFilters.orderKey = req.query.orderKey;
    }

    if (req.query.orderRule) {
        enabledFilters.orderRule = req.query.orderRule;
    }


    if (req.query.page) {
        enabledFilters.page = req.query.page;
    }

    if (req.query.rows) {
        enabledFilters.rows = req.query.rows;
    }

    return enabledFilters
}



app.get("/blogs", async (req, res) => {
    const enabledFilters = checkFilteringQuery(req);

    const result = await getAll(enabledFilters);


    return res.status(200).json({
        data: result
    })
})

app.listen(3000, () => {
    console.log("[INFO] READY!!!")
})

如果您有点困惑,我很快就会在此服务器中解释。

  1. 创建一个简单的Express App

  2. 创建一个获取路线以检索博客数据

  3. ,我们只使用客户端 /用户选择通过请求传递的过滤器组合。我们通过简单地在checkFilteringQuery函数中检查它,然后将其传递到Blogservice的GetAll函数(将在稍后解释)。

接下来,这只是一个简单的任务,我们将在单独的文件中创建一个Prisma实例(只是在此处接近现实世界的情况:P)

prisma-instance.js

const { PrismaClient } = require("@prisma/client")

const prisma = new PrismaClient();


module.exports = {
    prisma
}

建立查询和服务

好吧,现在,这是魔术发生的地方,我们将尝试操纵Prisma的发现查询,以更加灵活,以符合我们动态过滤的最终用户要求。

服务/buildquery.js

const buildFilterQuery = (enabledFilters) => {
    const usedFilter = {
        where: {
            AND: []
        }
    };


    if (enabledFilters.filters) {

        /*
                Filters would look like this : 

                {
                    "filter1":["a","b"],
                    "filter2":["a"]
                }
            */


        // First, we'll loop through the key inside enabledFilters . filters

        // IT'S IMPORTANT THAT key of these filter object, reflect to the column in our database !
        for (const key in enabledFilters.filters) {

            // We'll store the filter values (array of value that we want to be inside where clause, to make the code more readable)
            const filterValues = enabledFilters.filters[key];



            /* In this part we'll include relational filter as well, such as user.fullName or user.username
                It's because, prisma support this, the where clause will looks like : 

                where:{
                    relation:{
                        column : "value"
                    }
                }

            */
            if (key.includes(".")) {
                let [relation, column] = key.split(".");

                /*If values array length is just 1 , we'll just filter for exact match 

                 Pretty much like : 

                    prisma.blog.findMany({
                        where:{
                            relation:{
                                column: "value"
                            }
                        }
                    })

                */

                if (filterValues.length === 1) {
                    usedFilter.where.AND.push({
                        [`${relation}`]: {
                            [`${column}`]: filterValues,
                        },
                    });
                }

                /*But if values array length is more than 1 , we'll filter with `in` operator

                 Pretty much like : 

                    prisma.blog.findMany({
                        where:{
                            relation:{
                                column: {
                                    in : ["value1", "value2"]
                                }
                            }
                        }
                    })

                */
                else {
                    usedFilter.where.AND.push({
                        [`${relation}`]: {
                            [`${column}`]: { in: filterValues,
                            },
                        },
                    });
                }
            }
            /* This next part, is for filtering column that is available in our table 

                where:{
                    column:"value"
                }

            */
            else {
                if (filterValues.length === 1) {
                    usedFilter.where.AND.push({
                        [`${key}`]: filterValues[0]
                    })
                } else {
                    usedFilter.where.AND.push({
                        [`${key}`]: { in: filterValues,
                        },
                    });
                }
            }
        }
    }



    if (enabledFilters.searchKey && enabledFilters.searchValue) {
        /*
            Same logic as filter applied here, if the searchKey include ".", then it means we are searching based on relation
        */
        if (enabledFilters.searchKey.includes(".")) {
            let [relation, column] = enabledFilters.searchKey.split(".");
            usedFilter.where.AND.push({
                [`${relation}`]: {
                    [`${column}`]: {
                        contains: `${enabledFilters.searchValue}`,
                    },
                },
            });
        } else {
            usedFilter.where.AND.push({
                [`${enabledFilters.searchKey}`]: {
                    contains: `${enabledFilters.searchValue}`,
                },
            });
        }
    }



    return usedFilter;
}

现在,该代码看起来可能有点疯狂,但是现在,让我们尝试根据我已经在此处发布的评论来理解代码,然后在测试部分期间,我将其分解有关在各种情况下如何应用过滤器的情况。

现在,我们已经完成了查询,我们将Prisma的发现很多,从我们的博客服务中,

服务/blogservice.js


const { prisma } = require("../prisma-instance");
const { buildFilterQuery } = require("./buildQuery");

const getAll = async(filters) => {

    const usedFilter = buildFilterQuery(filters);

    const blogs = await prisma.blogs.findMany({
        ...usedFilter,
        include: {
            writer: true
        }
    })

    return blogs
}


module.exports = {
    getAll
}

测试和解释

好吧,我已向您保证,几分钟前的疯狂外观代码将在我们测试功能

时将在这里解释

下面显示的数据是通过播种插入的,您可以在我在设置部分上列出的存储库上找到它!

测试1:按博客类别过滤

  • 通过一个类别过滤

Image description

好吧,这里发生了什么:

  1. 我们提供:
  2. 的请求查询

{"category":["BACKEND"]

我们的buildFilterQuery函数转换为:

where:{
    AND:[
       {category:"BACKEND"}
    ]
}

将其与blogService.js中的完整Prisma查询结合在一起,实际上看起来像这样:

prisma.blogs.findMany({
   where:{
    AND:[
       {category:"BACKEND"}
    ]
   },
   include:{
     writer:true
   }
})

如果我们想用BACKENDFRONTEND的类别过滤博客怎么办?

简单!我们只将其传递到请求查询中:

{"category":["BACKEND","FRONTEND"]

它将转换为:

where:{
    AND:[
       {category: {in:["BACKEND","FRONTEND"]}
    ]
}

将其与blogService.js中的完整Prisma查询结合在一起,实际上看起来像这样:

prisma.blogs.findMany({
   where:{
    AND:[
        {category: {in:["BACKEND","FRONTEND"]}
    ]
   },
   include:{
     writer:true
   }
})

请注意,如果我们给出多个参数,则需要使用“ In”运算符。

测试2:按类别进行过滤 +搜索

请求看起来像这样:
Image description

然后,我们的buildFilterQuery函数将仅将其转化为:

where:{
    AND:[
       {category: "BACKEND"},
       {title:{
           contains : "Part 2"
         }
       }
    ]
}

,完整查询将是:

prisma.blogs.findMany({
   where:{
     AND:[
       {category: "BACKEND"},
       {title:{
           contains : "Part 2"
         }
       }
    ]
   },
   include:{
     writer:true
   }
})

第3部分(奖金)按类别进行过滤,并按作者的名字进行搜索

哦,是的,我们可以做到!
如果您查看过滤器查询,则进行过滤和搜索,我们是否有条件可以检查过滤器键或searchKey是否包含“”。它的内部,

要使用它,我们将简单地做:

Image description

,我们的buildFilterQuery将其转换为:

where:{
    AND:[
       {category: "BACKEND"},
       {
         writer:{
           fullName:{
              contains : "Writer 2"
           }
         }
       }
    ]
}

,完整查询将是:

prisma.blogs.findMany({
  where:{
    AND:[
       {category: "BACKEND"},
       {
         writer:{
           fullName:{
              contains : "Writer 2"
           }
         }
       }
     ]
   },
   include:{
     writer:true
   }
})

终于,我们得到了一个对象关系映射器,该映射器确实可以与对象Manipulaton合适!

这样,您甚至可以拥有更多的端点,并且只需要为应用程序中的所有不同实体应用此一个过滤功能即可。

请继续关注下一个部分,我们将在订购,供电(使用光标和限制偏移)之类的情况下进行更多的操作。

欢呼!

github存储库:here

与我联系:
LinkedIn