MsABON automatically discovers MS SQL tables, views, stored procedures, and scalar-valued functions matching configured filters and exposes REST endpoints for them. It also generates OpenAPI (Swagger) documentation for quick testing.
MsABON is built on NodeJS, which can be installed in a variety of ways: installed from exe installer (admin required), unzipped and paths mapped or locally executed (no admin), or with a tool like scoop or chocolatey (no admin, usually). This was originally built and tested on both mapped and locally executed installations of v22.21.1 LTS NodeJS. Here is the download link:
https://nodejs.org/en/download
Once NodeJS is installed, proceed with the setup.
- Install dependencies:
npm install-
Copy and rename
config.yaml.exampletoconfig.yaml -
Edit
config.yaml(project root) to describe one or more connections and app settings. Example:
http: http
host: 127.0.0.1
port: 3000
swaggerPath: /swagger
logLevel: verbose
connections:
- endpoint: __ENPT1__
server: __SRVR1__
port: 1433
username: __USER1__
password: __PASS1__
database: __DTBS1__
advanced: false
include:
tables:
- "__REGX1__"
- "__TABL1__"
views:
- "__REGX2__"
- "__TABL2__"
procedures:
- "__REGX3__"
- "__TABL3__"
functions:
- "__REGX4__"
- "__TABL4__"
- endpoint: __ENPT2__
server: __SRVR2__
port: 1433
username: __USER2__
password: __PASS2__
database: __DTBS2__
advanced: false
include:
tables:
- "__REGX5__"
- "__TABL5__"
views:
- "__REGX6__"
- "__TABL6__"
procedures:
- "__REGX7__"
- "__TABL7__"
functions:
- "__REGX8__"
- "__TABL8__"Notes about the config fields
http: default server http or https protocol (can be overridden withHTTPenv var).host: default server hostname / IP address (can be overridden withHOSTenv var).port: default server port (can be overridden withPORTenv var).swaggerPath: where Swagger UI is served (default/api-docsif not set).logLevel: currently respected informally; logger printsinfo&verbosemessages.connections: list of connection entries. Each entry:endpoint: logical name used in the HTTP path and OpenAPI component names.server,port,username,password,database: DB connection info.advanced: whentrue, enables the "Advanced" endpoint at/{endpoint}/afor base64-encoded SQL templates (read-only).include: per-type lists of patterns or names that are OR-combined. Patterns support:^prefixfor anchored prefix matches,- SQL LIKE wildcards (
%and_), - Plain names for exact matches.
- Start server:
npm start- The server tries to connect to each entry in
connections. For each successful connection it:- discovers tables and views matching
includetypes (SQL LIKE semantics), - introspects columns and primary keys, and
- registers routes under:
/{endpoint}/t/{table}for tables/{endpoint}/v/{view}for views/{endpoint}/p/{procedure}for stored procedures/{endpoint}/f/{function}for functions
- discovers tables and views matching
- Functions and Procedures are execute-only (POST only).
- Views are read-only (GET only).
- Tables support CRUD when a primary key is present:
GET /{endpoint}/t/{table}list (with filters and sorting/pagination)GET /{endpoint}/t/{table}/{id}get by PKPOST /{endpoint}/t/{table}createPUT /{endpoint}/t/{table}/{id}update by PKDELETE /{endpoint}/t/{table}/{id}delete by PK
- Advanced:
POST /{endpoint}/aexecutes a base64-encoded, read-only SQL template with simple{{ var }}substitutions.
Examples
- List rows:
GET /{endpoint}/t/pgm_Products - Get by id:
GET /{endpoint}/t/pgm_Products/{id} - Execute procedure:
POST /{endpoint}/p/usp_pgm_GetProductSpecswith body{"prodId": 12} - Execute scalar function:
POST /{endpoint}/f/fn_chkProductCategorywith body{"prodModel": "ABC123", "catId": 7} - Execute advanced:
POST /{endpoint}/awith body{"date":"03-28-2025","prodId":"2","data":"U0VMRUN.."}(truncated for clarity)
GET /--> JSON{"ok": true}health check.GET /{endpoint}/--> lists discovered objects for the endpoint, grouped by type.GET /{endpoint}/{type}--> lists discovered objects for the endpoint of that type.
Example discovery payload:
{
"endpoint": "api",
"tables": ["pgm_Access","pgm_Products"],
"views": ["LatestWidgetsView"],
"procedures": ["usp_pgm_GetProductSpecs","usp_pgm_SearchProductSpecs"],
"functions": ["fn_chkProductCategory","fn_chkProductState"]
}Note: The advanced endpoint /{endpoint}/a is not a discovery endpoint and will return a 404 error: Cannot GET /{endpoint}/a.
The list endpoints (GET /{endpoint}/t/{table} and GET /{endpoint}/v/{view}) support these default query parameters:
select: Comma-separated list of columns to return. Unknown column names are ignored.- Example:
?select=id,date
- Example:
order: Sort order in the formatcolumn.ascorcolumn.desc. Default is ascending (ASC).- Example:
?order=id.desc
- Example:
limit: Number of rows to return.-1returns all (default).- Example:
?limit=50
- Example:
offset: Number of rows to skip before starting the result set. Default0.- Example:
?offset=100
- Example:
Filter any column via query string:
- Examples of basic filters:
- Literals:
GET /api/t/pgm_Products?Month=3&Year=2026 - Strings:
GET /api/t/pgm_Products?Name=Widget&Status=Active
- Literals:
- Only these null/boolean filter tokens are recognized (case-insensitive):
is.null-->IS NULLnot.null-->IS NOT NULLis.true-->TRUE(BIT) or1(INT-like)is.false-->FALSE(BIT) or0(INT-like)
- Wildcards accepted:
^for prefix and%for wilcard.- Example: ?Name=^Widg&Line=%500%
- To filter columns actually named
order,limit,offset, orselect, prefix the column with~.- Example:
?~order=is.false
- Example:
- Combine multiple acceptable values for a single column using commas for
OR:- Literals:
?status=3,4-->[status] IN (3,4) - Tokens:
?isActive=is.null,is.false-->([isActive] IS NULL OR [isActive] = 0) - Strings:
?state=NY,CA-->[state] IN ('NY','CA')
- Literals:
- Filter by ranges using math convention:
()exclusive,[]inclusive, with:between endpoints:(x:)-->> x[x:)-->>= x(:x)-->< x(:x]--><= x(x:y)-->x < col < y[x:y]-->x <= col <= y[x:y)-->x <= col < y(x:y]-->x < col <= y
- Exclude results using the
!operator:- Single value:
?~state=!CA-->[state] <> 'CA' - Combination with other values:
?id=(10:20),42,!15-->((id >= 10 AND id <= 20) OR id = 42) AND (id <> 15)
- Single value:
Putting it together:
GET /api/t/pgm_Products?order=id.desc&limit=25&offset=50&select=id,dateGET /api/t/pgm_Products?order=CreatedAt.asc(default ASC if direction omitted)GET /api/t/pgm_Products?select=id,date&order=id.desc&limit=1&enabled=is.null,is.false&~offset=0,1,2GET /api/t/pgm_Products?id=(0:20],42,!15&select=name,description
- Swagger UI is available at
http://localhost:<port>/<swaggerPath>(default/api-docsif not set). - OpenAPI JSON served at
http://localhost:<port>/swagger.json. - The UI loads the spec from
/swagger.json, and groups endpoints under tags by type:Advanced(read-only)Functions(execute-only)Procedures(execute-only)Views(read-only)Tables(CRUD where applicable)
The server prints a clickable link to Swagger UI on startup.
- The console logger is verbose by default; you will see timestamped
INFO,WARN,ERROR, andVERBOSEmessages about discovery and executed SQL (parameters are shown; passwords are not printed). - Database passwords are not printed.
- Advanced endpoint is default off and filters out common dangerous tokens (e.g., INSERT, UPDATE, DELETE, CREATE, DROP, MERGE, EXEC, USE, BEGIN TRAN/COMMIT/ROLLBACK).
- Default port:
3000(inconfig.yamlorPORTenv var). - Default SQL port:
1433whenportis not provided in a connection entry. - List defaults:
order-> ASC on PK or first column,limit->-1,offset->0.
- To change the config file name/location, before starting (
npm start), setCONFIG_PATHenv var ($env:CONFIG_PATH = 'config.yaml'). - To run multiple APIs from different servers/databases, add multiple entries to
connections; eachendpointyields its own namespaced routes and OpenAPI components. - If a connection fails (e.g., DNS down), the server retries every 30 seconds without stopping. When the connection succeeds, routes and the Swagger spec are updated automatically.
- Path:
POST /{endpoint}/a - Body shape:
- data (required): base64-encoded SQL template string
- Any additional keys are treated as template variables (e.g.,
{{ date }},{{ lineId }}) - Optional rowLimit (integer, default 1000) to cap rows
- Templating:
{{ var }}placeholders are replaced from the JSON body.- Numbers are inserted as-is; strings have single quotes escaped.
- Read-only guard:
- DML/DDL/EXEC and other dangerous tokens are blocked (e.g., INSERT, UPDATE, DELETE, CREATE, DROP, MERGE, EXEC, USE, BEGIN TRAN/COMMIT/ROLLBACK).
- Response: JSON array of rows (recordset).
Example:
SELECT
SUM(widgets) AS totalWidgets
FROM
[Database].[dbo].[LatestWidgetsView]
WHERE
[var_1] = '{{ example_var_1 }}' and [var_2] = {{ example_var_2 }}Base64 for the SQL goes in data; variables live alongside it:
{
"exampe_var_1": "example_value_1",
"exampe_var_2": "example_value_2",
"data": "U0VMRUNUIA0KICAgIFNVTSh3aWRnZXRzKSBBUyB0b3RhbFdpZGdldHMNCkZST00gDQogICAgW0RhdGFiYXNlXS5bZGJvXS5bTGF0ZXN0V2lkZ2V0c1ZpZXddDQpXSEVSRSANCiAgICBbdmFyXzFdID0gJ3t7IGV4YW1wbGVfdmFyXzEgfX0nIGFuZCBbdmFyXzJdID0gIHt7IGV4YW1wbGVfdmFyXzIgfX0=",
"rowLimit":2
}This project was generated with AI, so it is not copyrightable in certain jurisdictions. Use at your own risk.