企业内部经常会出现这样的需求:运营需要一个数据看板,测试需要一批测试账号管理页面,客服需要查询用户订单,财务需要核对账单状态。这类系统通常功能不算复杂,但“麻雀虽小五脏俱全”,如果每一套都由开发从头搭建,从登录权限到前端表单再到后端查询,少则一两周,多则一两个月。而这类工具的使用频率和业务价值又非常高,一旦做得不好,企业内部流程就会卡在“信息不透明”上。ToolJet 就是围绕这个痛点出现的开源低代码平台,它把数据源接入、界面搭建、查询逻辑和权限控制统一放进一个可视化工作台里,让开发者和业务人员都能快速搭建内部系统。本文会带你完整理解 ToolJet 的核心概念,完成从部署到搭建一个实用管理后台的全过程,并整理自托管运行时最常见的排错经验与工程建议。
作为一篇偏实战的教程,我更关注怎么把 ToolJet 跑起来,而不是停留在“它能做什么”的功能清单。读完本文后,你可以掌握 ToolJet 的部署方式,理解数据源、查询、组件、事件和数据绑定之间的关系,并且能亲手做出一个基于 PostgreSQL 的员工数据管理面板,包括员工列表、关键字筛选、新增记录、删除记录和部门统计图表。如果你之前没有接触过低代码平台,文章也会先解释它和传统开发的区别,保证零基础也能跟上操作步骤。
1. ToolJet 是什么
1.1 内部系统研发的痛点
大多数公司都不会把内部业务系统当成对外产品来打磨,但这不代表它们不重要。一个运营团队每天需要的数据,往往散落在多个数据库、表格和第三方系统里;研发同学如果把时间都花在重复开发内部后台,核心业务迭代就会被拖慢;如果直接给业务人员一个 SQL 客户端,又不够安全,也不够直观。
传统开发模式要解决的问题其实很清楚:连接数据、写查询逻辑、做展示页面、加权限控制、部署上线。问题是这套流程太重了。每个内部工具都需要工程初始化、目录结构、前后端联调、环境配置,可能还要考虑 Docker 镜像和服务器资源。当需求数量变多,这种重流程会让内部工具的需求排期越来越长。
低代码平台的价值,就是把“页面层”和“数据层”中间的重复工作抽象掉。ToolJet 正是这样一类工具:它并不消灭代码,而是把代码的位置从“写一个完整 Web 应用”收敛到“写一个查询片段”或“写一段数据转换函数”。团队可以在很短时间内从需求澄清走到可用的原型,再通过平台的自托管能力把它变成生产可用的系统。
1.2 ToolJet 的定义
ToolJet 是一个开源的低代码平台,主要用来快速构建企业内部工具。你可以在浏览器中通过拖拽组件的方式搭建页面,也可以连接 MySQL、PostgreSQL、MongoDB、REST API、Google Sheets 等常见数据源,然后编写 SQL 或 API 请求完成数据查询与写入。最终页面可以发布给团队成员,并按照角色分配访问权限。
换句话说,ToolJet 尝试把“后台管理页面”的开发过程变得更像拼装积木。开发者不再需要从零创建 Spring Boot 或 Node.js 服务,也不需要在自己的项目里维护前端表格组件,而是把 Table、Form、Chart、Modal 等可视化组件拖到画布上,再用表达式与查询结果绑定起来。ToolJet 也和传统代码生成器不同,它并不是生成一套代码让你拿走去部署,而是直接运行平台本身,让应用跑在 ToolJet 的服务中。
由于 ToolJet 是开源且支持自托管的,它对数据私密性要求较高的团队非常友好。你可以把 ToolJet、它的数据库以及所有业务数据源都部署在自有机房或云服务器中,降低第三方 SaaS 带来的数据出境顾虑。
1.3 ToolJet 适合哪些人和场景
ToolJet 的目标用户并不是完全不懂技术的业务人员,而是有一定数据处理经验,但又不希望每次都用全套工程化流程去开发内部系统的开发者。后端工程师可以用它快速搭建给运营使用的查询后台,数据分析师可以用它创建自己的数据筛选界面,运维工程师也可以用 ToolJet 做一个简单的配置管理平台。
比较适合 ToolJet 的场景通常有几个共同特点:第一,功能以数据“增删改查”为主,不需要复杂度特别高的状态机;第二,页面交互集中在表格、表单、下拉框、弹窗和图表这些常见组件上;第三,用户量不大,几十到几百人使用即可满足需求;第四,迭代速度快,可能下周就要调整字段或增加新的查询条件。
但 ToolJet 不适合用来做面向海量用户的高并发 C 端系统。你仍然需要专业后端服务来处理复杂权限、性能优化和业务一致性。ToolJet 的定位是提高内部工具的研发效率,而不是替代所有后端开发技术。
1.4 ToolJet 的典型应用场景
在我接触过的项目中,ToolJet 最常用的几类场景包括:运营数据看板,比如把数据库中的订单、用户、渠道数据以表格和图表形式集中展示;后台管理界面,比如用户管理、优惠券配置、Content 内容上下架;数据修复工具,比如由研发或 DBA 输入条件后批量更新线上数据的操作页;接口联调面板,把需要访问的第三方 API 统一封装成可控查询,并在页面上快速验证结果。
一个典型页面可能长这样:顶部是筛选条件,中间是统计卡片或图表,下方是明细数据表格。传统开发这套页面需要写前端列表页、封装接口、处理查询参数、考虑分页问题;而在 ToolJet 中,只需要把 SQL 查询写好,把 Table 组件拖进画布,再绑定数据即可。如果你熟悉 SQL 和 JavaScript,会把 ToolJet 的使用效率再提升一个数量级。
2. ToolJet 的核心概念拆解
2.1 数据源与查询:一切页面的数据入口
在 ToolJet 中,“数据源”代表一个你已经配置好的连接目标,比如一台 PostgreSQL 实例、一个 MySQL 库、一个 MongoDB 集合,或者一个可供访问的 REST API。数据源配置是一次性的,一个数据源可以被多个查询复用。这样配置的好处是,当数据库连接地址发生变化时,你只需要修改数据源,不用逐页修改组件。
“查询”则是在某个数据源上执行的操作。对数据库来说,查询可以是一条 SQL;对 API 来说,查询可以是一个 HTTP 请求。ToolJet 会保存查询定义,你可以随时手动运行它,也可以在页面按钮或组件事件中自动触发它。查询返回的数据会被保存为查询结果,供 Table、Chart、Dropdown 等组件绑定。
这里尤其需要区分两个数据库概念:ToolJet 自身元数据库和业务数据库。ToolJet 服务端本身需要一个数据库来保存应用定义、用户账号、查询配置和页面快照等信息;而你在数据源里配置的业务数据库,才是页面要操作的真实业务数据。日常维护时不要把两者混为一谈,尤其是执行备份和权限清理时,要分别对待。
2.2 组件与数据绑定:了解双花括号表达式
ToolJet 的界面由各种可视化组件组成,包括文本、按钮、表格、输入框、下拉选择器、日期选择器、Modal 弹窗、图表、容器等。每个组件都可以重命名,并拥有一组可配置的属性。为了让页面真正驱动起来,我们需要把组件产生的值和查询返回的数据连接起来,这个动作就叫数据绑定。
ToolJet 最常见的绑定语法是双花括号{{ }}。你会在属性面板中填写类似下面的表达式:
{{queries.getEmployees.data}}它的含义是:将getEmployees这个查询返回的数据,绑定到当前组件的某个属性上。如果绑定到 Table 的 Data 属性,表格就会显示查询结果;如果绑定到 Text 的 Content 属性,页面就会输出数据内容。
除了查询数据,组件的值也能被读取。比如我们把一个文本输入框命名为searchInput,后续就可以通过{{searchInput.value}}拿到用户输入的内容。又比如绑定 Table 的选中行属性,可以使用{{employeeTable.selectedRow.id}}拿到当前选中记录的主键。理解这一层,就能玩转 ToolJet 的交互逻辑。
这里补充一个新手最常见的误区:在 ToolJet 的表达式里,组件名和查询名要尽量使用英文字母、数字和下划线,如果用中文命名,不少表达式解析版本会报错或者在某些渲染环境下出现编码问题。后续所有示例我都会采用英文命名。
2.3 事件处理器与页面发布机制
组件只是静态展示还不够,ToolJet 想要完成业务流程,还需要“事件处理器”。事件处理器是页面交互逻辑的核心入口,比如用户点击按钮后发生什么、查询成功后发生什么、Modal 关闭后发生什么。我们可以为按钮配置事件:点击按钮时运行某个查询、打开弹窗、展示提示、刷新某个查询。
一个比较常见的设计是:点击“保存”按钮,先运行insertEmployee查询,数据插入成功后,再运行getEmployees查询刷新表格。这样页面上的操作顺序就变成了:用户操作组件 -> 触发事件 -> 执行查询 -> 绑定数据刷新界面。
ToolJet 的应用页面分为编辑态、预览态和已发布状态。编辑态用于开发调试,预览态让你在发布前检查体验,已发布状态才会被普通成员看到。在实际开发中,建议把页面搭建完成并验证查询绑定正确后,再点击发布。发布后的地址可以分享给其他团队成员,ToolJet 会按照账号权限决定谁能访问、谁能编辑、谁能查看。
3. ToolJet 环境准备与自托管部署
3.1 部署方式选择
使用 ToolJet 有两种主流方式。第一种是使用官方托管的云服务,注册后可以直接创建 Workspace 开始搭建应用,适合想快速验证产品能力和不想维护服务器的小团队。第二种是自托管,把 ToolJet 的 Docker 镜像部署到自己的服务器上,适合对数据安全、私网连接和定制化要求更高的团队。
本文重点介绍自托管方式。自托管带来的好处是数据完全在自己环境内流转,但同样意味着你需要承担运维责任,比如数据库备份、镜像升级、访问日志监控和系统资源观察。如果团队没有专职运维,建议先在一台配置足够的 Linux 服务器上运行,并且一定要预留数据卷备份目录。
开始前你需要准备一台 Linux 服务器或本地虚拟机,建议系统内存不低于 4GB,磁盘空间根据内部工具数量预留至少 20GB。服务器需要安装 Docker 和 Docker Compose。以下安装步骤以常见环境为例,不同操作系统细节会有差异,重点演示配置思路,请以你的实际环境版本为准。
3.2 使用 Docker Compose 快速部署
为了把 ToolJet 自身的元数据库和应用服务一起管理起来,我们使用 Docker Compose 定义两个服务:ToolJet 主服务和 PostgreSQL 元数据库。以下是最小化的部署示例:
# 文件路径:docker-compose.yml version: "3" services: tooljet: image: trytooljet/tooljet:latest restart: unless-stopped ports: - "8080:8080" environment: TOOLJET_DB_HOST: postgres TOOLJET_DB_USER: tooljet TOOLJET_DB_PASSWORD: change_me_strong_password TOOLJET_DB_NAME: tooljet # 认证加密相关密钥,建议用 openssl rand -hex 32 生成 # SECRET_KEY_BASE: <random> # LOCKBOX_MASTER_KEY: <random> depends_on: - postgres postgres: image: postgres:14 restart: unless-stopped environment: POSTGRES_USER: tooljet POSTGRES_PASSWORD: change_me_strong_password POSTGRES_DB: tooljet volumes: - tooljet_pgdata:/var/lib/postgresql/data volumes: tooljet_pgdata:这里需要特别提醒,部署演示中我把一部分环境变量按常见写法列了出来,但不同版本对密钥变量和初始化参数的命名可能存在差异。实际生产环境请直接参考官方最新的 docker-compose 模板和 release release 文档,不要照抄后直接上线。尤其要注意把change_me_strong_password和注释中的随机密钥替换成足够长的随机内容。
启动命令比较简单:
docker compose up -d启动过程首次会拉取镜像并初始化数据库,可能需要等待几分钟。运行以下命令观察日志:
docker compose logs -f tooljet当你看到 ToolJet 输出服务启动成功的日志后,打开浏览器访问http://服务器IP:8080,即可进入 ToolJet 的初始化页面。
3.3 初始化与运维要点
首次进入 ToolJet 页面时,你需要根据系统提示创建管理员账号,然后进入工作台。后续添加团队成员的方式也是通过平台的内置账号管理功能完成。管理员账号拥有全部权限,可以管理组织内成员、创建数据源、编辑应用和发布应用。
日常运维有几个关键点必须注意。
第一,密钥和数据库密码一定要保存好,尤其是加密密钥一旦丢失,可能导致已有会话和应用加密数据无法解密,恢复过程会非常痛苦。建议把密钥放到独立的.env文件中,并确保该文件不会被提交到代码仓库。
第二,要定期备份 PostgreSQL 元数据库。ToolJet 的应用页面结构、查询定义都保存在元数据库中,业务数据反而不会存进来。你可以通过 PostgreSQL 的pg_dump工具或者云数据库自带备份功能执行备份,工具核心配置才不会因服务器故障丢失。
第三,镜像升级前先查看官方更新说明。ToolJet 迭代速度较快,升级可能带来数据库结构迁移或配置项调整。建议先在测试环境完成一次完整升级,再对生产环境操作。升级前一定要备份元数据库和.env配置文件。
4. 实战:用 ToolJet 搭建员工数据管理面板
4.1 需求分析与数据表准备
为了把前面的概念串起来,这一节我们实现一个非常典型的企业内部管理页面:员工数据管理面板。业务需求如下:能查看员工列表,能按关键字筛选姓名或部门,能新增员工,能删除选中的员工,还能按部门统计员工人数。整个场景没有复杂的业务流程,很适合用 ToolJet 来演示。
先准备一张简单的 PostgreSQL 员工表。为了减少环境依赖,我们在 ToolJet 连接的业务 PostgreSQL 数据库中执行以下 SQL:
-- 创建员工示例表 CREATE TABLE IF NOT EXISTS employees ( id SERIAL PRIMARY KEY, name VARCHAR(50) NOT NULL, department VARCHAR(50) NOT NULL, email VARCHAR(100), status VARCHAR(20) DEFAULT 'active', created_at TIMESTAMP DEFAULT NOW() ); -- 插入几条示例数据 INSERT INTO employees (name, department, email, status) VALUES ('张明', '研发部', 'zhangming@example.com', 'active'), ('李华', '运营部', 'lihua@example.com', 'active'), ('王强', '数据部', 'wangqiang@example.com', 'inactive'), ('赵敏', '市场部', 'zhaomin@example.com', 'active'), ('刘洋', '研发部', 'liuyang@example.com', 'active');这里使用SERIAL自增主键,创建时间默认使用当前时间。项目中使用 PostgreSQL 14 及以上版本均可。你完全可以把表结构换成自己业务中的订单表、商品表或审计日志表,核心思路是一样的。
4.2 在 ToolJet 中配置 PostgreSQL 数据源
进入 ToolJet 工作台后,先创建一个新的应用。在应用编辑区左侧找到数据源管理入口,选择 PostgreSQL,然后填写数据库连接信息。这里的数据库地址要填写你的业务 PostgreSQL 可访问的地址,用户名建议使用只读和可写权限分离的账号,并根据实际需要限制访问。
ToolJet 通常会提供 Test Connection 按钮,点击后显示连接成功,说明配置无误。连接成功后,我们可以创建第一个查询,命名为getEmployees,数据源选择刚才创建的 PostgreSQL 数据源,查询内容为:
SELECT id, name, department, email, status, created_at FROM employees ORDER BY created_at DESC;点击 Run 按钮执行查询,如果下方展示出刚刚插入的 5 条数据,说明查询链路已经打通。这个查询用于加载表格初始数据,后续组件中的 Table 会直接绑定它的返回结果。
为了支持前端关键字搜索,我们还可以在查询中添加一个 JavaScript Transformations。ToolJet 的 Transformations 允许在查询结果展示到组件之前做一次数据处理。我们在该查询的 Transformations 区域写入:
// 将查询返回的 data 根据搜索框输入进行过滤 const keyword = String(searchInput.value || '').trim().toLowerCase(); if (!keyword) { return data; } return data.filter((item) => [item.name, item.department, item.email, item.status] .join(' ') .toLowerCase() .includes(keyword) );这段代码会读取页面上名为searchInput的输入框组件内容。如果搜索框为空,就返回原始数据;如果用户输入了关键字,就按姓名、部门、邮箱、状态做一次前端过滤。这样搜索不需要反复请求数据库,适合当前这种数据量不大的内部工具场景。
4.3 搭建页面组件并绑定数据
回到应用画布,我们开始搭建界面。在组件库中选中 Table 组件并拖入画布,将组件重命名为employeeTable。在 Table 组件的属性面板中,找到 Data 字段并填入:
{{queries.getEmployees.data}}保存后进入预览,表格应该能自动根据查询结果渲染出id、name、department等列。如果列没有自动生成,可以通过 Table 的 Column Definition 手动维护列,并将字段名与 SQL 返回字段保持一致。
接下来在页面顶部放入一个文本输入框,重命名为searchInput,并在它的 onChange 事件中绑定一个动作:运行查询getEmployees。这样用户每次输入关键字并触发变更后,Transformations 会重新读取搜索内容,前端表格数据也就同步更新了。再往页面中放入两个按钮:openAddModalBtn用于打开新增员工弹窗,deleteEmployeeBtn用于删除当前表格选中的员工。
组件全部放入后,可以借助右侧的组件树检查命名是否清晰:employeeTable、searchInput、openAddModalBtn、deleteEmployeeBtn、saveEmployeeBtn。准确命名虽然不影响功能,但能让后续合作维护的人少走弯路。
4.4 实现新增、删除和部门统计
新增员工功能需要一个 Modal 弹窗。在组件库中拖入 Modal,在里面放置姓名输入框empNameInput、部门下拉框empDeptSelect、邮箱输入框empEmailInput、状态下拉框empStatusSelect和保存按钮saveEmployeeBtn。把打开弹窗按钮的事件设置为 Open Modal。
然后创建保存员工查询insertEmployee,SQL 内容如下:
INSERT INTO employees (name, department, email, status, created_at) VALUES ( '{{empNameInput.value}}', '{{empDeptSelect.value}}', '{{empEmailInput.value}}', '{{empStatusSelect.value}}', NOW() );这里通过双花括号把弹窗表单组件中的值取出来,注入到 SQL 中。ToolJet 在运行时会把表达式的值替换成组件的当前值。需要说明的是,字符串字段外面要加单引号;如果组件值本身包含危险字符,会产生 SQL 拼接风险,因此生产环境要尽量限制页面编辑权限并完善输入校验。插入成功后,在保存按钮事件上继续配置:运行insertEmployee,然后运行getEmployees刷新表格,最后关闭 Modal。
删除选中员工功能相对直白。创建一个删除查询deleteEmployee,为了避免用户没有选中任何行时产生误删整表风险,SQL 中特意加了|| -1兜底:
DELETE FROM employees WHERE id = {{employeeTable.selectedRow.id || -1}};如果当前表格没有选中行,selectedRow.id会是空值,表达式整体会变成-1,删除操作影响 0 行,不会清空整表。然后把删除按钮的事件配置为运行deleteEmployee,成功后再次运行getEmployees刷新表格。页面中还应该给删除按钮设置一定的防误触体验,比如事件链中先弹确认提示,或者依赖 ToolJet 的确认能力。
部门统计功能使用图表组件完成。先创建统计查询departmentStat:
SELECT department, COUNT(*) AS cnt FROM employees GROUP BY department ORDER BY cnt DESC;运行后返回每个部门的员工数量。接着往页面拖入一个 Chart 组件,并把它绑定到{{queries.departmentStat.data}}。ToolJet 的图表配置项和组件版本有关,你可以在 Chart 组件的 Data 字段中选择该查询,并指定 X 轴为department,Y 轴为cnt。预览时就能看到柱状图或饼图。
4.5 运行验证与结果说明
到这里,一个简单的员工管理面板已经成型。整个应用验证流程建议按下面的步骤走一遍:第一步,确认getEmployees查询能手动跑通并返回 5 条数据;第二步,进入预览模式,检查 Table 是否正确显示员工列表,部门统计图表是否出现;第三步,在搜索框输入“研发”或“数据”,确认表格按部门过滤成功;第四步,点击打开新增弹窗,填写姓名、部门和邮箱,点击保存,确认表格新增记录并关闭弹窗;第五步,在表格中选中一条记录,点击删除按钮,确认该记录消失。
如果上述步骤全部通过,就可以点击发布按钮,把应用发布给团队成员使用了。发布后的页面是独立 URL,成员根据分配的账号角色访问,没有账号的用户无法直接打开。ToolJet 在内部工具场景中通常不会把页面匿名公开,而是通过平台的账号权限来管理入口。
5. ToolJet 常见报错与排查思路
5.1 高频问题对照表
不管是自托管部署阶段还是页面配置阶段,ToolJet 都会出现一些比较典型的问题。我把高频问题整理成下表,你在报错时可以先行对照。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 部署后页面无法访问 | 端口被占用或防火墙未放行 | 检查 docker compose 端口映射和系统防火墙策略 |
| 初次初始化数据库失败 | PostgreSQL 密码不对或网络不通 | 查看日志,确认数据库连接参数和依赖服务状态 |
| 运行查询提示连接失败 | 业务数据源地址或账号不可达 | 在数据源面板重新填写地址,执行 Test Connection |
| SQL 查询执行报错 | SQL 语法或变量拼接格式错误 | 先去掉双花括号表达式,测试纯 SQL 是否可运行 |
| Table 没有数据 | 查询返回的数据不是数组 | 运行查询查看返回数据结构,确认绑定字段是否正确 |
| 页面操作按钮无响应 | 事件处理器未配置或配置错误 | 检查目标按钮的 Events 设置,确认是否选择了 Run Query |
| 删除或更新 |