Same concept as resources in RESTful, it refers to objects that can be operated on and exposed externally by the system, which can be data tables, files, and other custom objects.
Operations mainly refer to reading and writing resources, typically used for viewing data, creating data, updating data, deleting data, etc. Tachybase implements resource access by defining operations, the core of which is essentially a middleware function for handling requests that is compatible with Koa.
Currently, resources mainly target data in data tables. By default, Tachybase automatically maps database tables to resources and also provides server-side data interfaces. So by default, as long as you define a data table using db.collection(), you can access the data resources of this data table through Tachybase's HTTP API. The name of the automatically generated resource is the same as the table name defined in the data table. For example, a data table defined with db.collection({ name: 'users' }) corresponds to a resource name of users.
At the same time, common CRUD operations are built-in for these data resources, and relational data resources also have built-in methods for operating associated data.
Default operations for simple data resources:
list: Query data list in data tableget: Query single data record in data tablecreate: Create single data record in data tableupdate: Update single data record in data tabledestroy: Delete single data record in data tableIn addition to simple CRUD operations, relational resources also have default relational operations:
add: Add association to dataremove: Remove association from dataset: Set association for datatoggle: Add or remove association for dataFor example, define a posts data table and sync it to the database:
After that, all CRUD methods for the posts data resource can be directly called via HTTP API:
When the default CRUD and other operations don't meet business scenarios, you can also extend more operations for specific resources. For example, additional processing of built-in operations, or setting default parameters.
Custom operations for specific resources, such as overriding the create operation in the posts table:
This adds reasonable restrictions in business logic, preventing users from creating posts under other user identities.
Custom operations for all resources globally, such as adding an export operation for all data tables:
Then you can export CSV format data via the following HTTP API:
When client requests reach the server, related request parameters are parsed according to rules and placed on the ctx.action.params object of the request. Action parameters mainly come from three sources:
Before the actual operation processing function handles it, these three parts of parameters are merged together in this order and ultimately passed into the operation's execution function. This is also true across multiple middleware, where parameters processed by the previous middleware continue to be passed with ctx to the next middleware.
For parameters that can be used with built-in operations, refer to the @tachybase/actions package content. Except for custom operations, client requests mainly use these parameters, and custom operations can extend needed parameters according to business requirements.
Middleware pre-processing mainly uses the ctx.action.mergeParams() method, and different parameter types have different merge strategies, which can also be referenced in the mergeParams() method content.
Default parameters of built-in Actions can only be merged according to the default strategy for each parameter in the mergeParams() method, to achieve certain operation restrictions on the server side. For example:
The above defines the create operation for the posts resource, where whitelist and blacklist are respectively the whitelist and blacklist for the values parameter, meaning only title and content fields in the values parameter are allowed, and createdAt and createdById fields in the values parameter are prohibited.
Data-type resources are further divided into independent resources and relational resources:
<collection><collection>.<association>Customization is mainly needed for non-database table resources, such as in-memory data, proxy interfaces for other services, etc., as well as cases where specific operations need to be defined for existing database table resources.
For example, define a resource for sending notification operations unrelated to the database:
Then in HTTP API you can access it like this:
We continue the simple shop scenario from the previous Data Tables and Fields Example to further understand concepts related to resources and operations. Here we assume further resource and operation definitions based on previous data table examples, so the data table content is not repeated here.
As long as corresponding data tables are defined, we can directly use default operations for data resources like products, orders, etc. to complete the most basic CRUD scenarios.
Sometimes, it's not just simple single-record operations, or default operation parameters need some control, so we can override default operations. For example, when creating an order, the userId should not be submitted by the client to represent order ownership, but should be determined by the server based on the currently logged-in user. In this case, we can override the default create operation. For simple extensions, we can write directly in the plugin's main class:
Thus, we override the default create operation for the orders data resource during plugin loading, but after modifying operation parameters, we still call the default logic without writing it ourselves. The mergeParams() method for modifying submission parameters is very useful for built-in default operations, which we'll introduce later.
When built-in operations can't meet business needs, we can extend resource functionality through custom operations. For example, an order typically has many statuses. If we design the status field's values as a series of enumerated values:
-1: Canceled0: Ordered, not paid1: Paid, not shipped2: Shipped, not received3: Received, order completedThen we can implement order status changes through custom operations. For example, a shipping operation for orders. Although simple cases can be implemented through update operations, if there are more complex situations like payment, receipt, etc., using only update would cause unclear semantics and parameter confusion. Therefore, we can implement through custom operations.
First, we add a delivery information table definition, saved to collections/deliveries.ts:
At the same time, extend the orders table with a delivery information association field (collections/orders.ts):
Then we add the corresponding operation definition in the plugin's main class:
Among them, Repository is the data repository class used for data tables, most data read and write operations will be completed by this, for details refer to the Repository API section.
After defining, we can call the "deliver" operation from the client via HTTP API:
Similarly, we can define more similar operations, such as payment, receipt, etc.
Suppose we want users to be able to query their own orders and only their own orders, and we need to restrict users from querying canceled orders. We can define this through action default parameters:
When users query from the client, they can also add other parameters in the request URL, such as:
The actual query conditions will be merged into:
And get the expected query results.
Additionally, if we need to restrict the order creation interface from allowing clients to submit order number (id), total price (totalPrice), and other fields, we can control this by defining default parameters for the create operation:
This way, even if the client deliberately submits these fields, they will be filtered out and won't exist in the ctx.action.params parameter set.
If there are more complex restrictions, such as only being able to place orders when products are on shelves and in stock, this can be implemented by configuring middleware:
Putting part of the business logic (especially pre-processing) into middleware can make our code clearer and easier to maintain.
Through the above examples, we've introduced how to define resources and related operations. Let's review the content of this chapter: