
Faasjs Best Practices
- 5 installs
- 1 repo stars
- Updated February 18, 2026
- faasjs/faasjs-skills
Helps with ai & agent building tasks.
About
faasjs-best-practices is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- faasjs-best-practices
- AI & Agent Building
- AI-coding skill
Faasjs Best Practices by the numbers
- 5 all-time installs (skills.sh)
- Ranked #13,065 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Jul 13, 2026 (Skillselion catalog sync)
npx skills add https://github.com/faasjs/faasjs-skills --skill faasjs-best-practicesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 5 |
|---|---|
| repo stars | ★ 1 |
| Last updated | February 18, 2026 |
| Repository | faasjs/faasjs-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
Apply these rules when writing or reviewing FaasJS code.
File conventions
See File conventions for:
- Project structure and special files
- Route segments and fallback (
*.func.ts,index.func.ts,default.func.ts) - Verb naming semantics and list endpoint conventions
defineFunc
See defineFunc guide for:
- When to use
defineFuncin*.func.tsfiles - How plugin config from
faas.yamlis auto-loaded - A complete endpoint example with typed params
Knex
See Knex rules for:
- Configuring Knex via
faas.yamland using query-builder methods overknex.raw
Related skills
- Unit testing: faasjs-unit-testing
defineFunc Guide
defineFunc is the default way to implement FaasJS *.func.ts handlers in this skill.
What it does
- Accepts business logic directly (
async ({ event, context }) => { ... }). - Auto-loads plugins from
func.config.pluginson first mount. - Keeps endpoint files focused on validation and domain logic.
Plugin config (src/faas.yaml)
defineFunc reads plugin settings from faas.yaml (loaded into func.config by @faasjs/load, @faasjs/server, or @faasjs/dev).
defaults:
plugins:
http:
config:
cookie:
secure: false
session:
secret: secretNotes:
plugins.<key>is the plugin name.- If
typeis omitted, the key is used as plugin type (http->@faasjs/http). - Set
typeexplicitly when plugin name and module type should differ.
Example Endpoint
src/pages/home/api/hello.func.ts:
import { defineFunc } from '@faasjs/func'
import { z } from 'zod'
const schema = z
.object({
name: z.string().optional(),
})
.required()
export const func = defineFunc<{ params?: z.infer<typeof schema> }>(
async ({ event }) => {
const parsed = schema.parse(event.params || {})
return {
ok: true,
data: `Hello, ${parsed.name || 'FaasJS'}`,
error: null,
}
}
)Common Errors
Failed to load plugin "...": checkplugins.<name>.typeand package installation.Failed to resolve plugin class "...": use a valid plugin module that exports a plugin class.Client not initialized: ensure required plugins (for examplehttp/knex) are configured insrc/faas.yaml.
File Conventions
FaasJS SPA + API uses zero-mapping routing: route paths are derived directly from *.func.ts file paths.
Core Principle
Where .func.ts lives is where the route lives. No extra alias, rewrite, or mapping layer.Project Structure
Recommended root: src/pages
src/pages/
page-a/
index.tsx
components/
header.tsx
api/
submit.func.ts
feature-a/
components/
panel.tsx
api/
create.func.ts
index.func.tsSpecial Files
| File | Purpose |
|---|---|
index.tsx | Page entry UI |
*.func.ts | API endpoint file |
index.func.ts | Segment root endpoint |
default.func.ts | Fallback endpoint |
Route Resolution Order
@faasjs/server resolves request URLs by probing file candidates in this order:
1. path.func.ts 2. path/index.func.ts 3. path/default.func.ts 4. Parent directory default.func.ts (fallback up the tree)
Route Segments
- Static segment: each directory or file name in a
*.func.tspath is a URL segment. - Catch-all segment: use
default.func.tsto match the rest of a path under a directory. - Dynamic segment: there is no built-in
[id]/[...slug]filename convention; usedefault.func.tsand parse path in handler logic. - Groups: there is no hidden group directory convention (for example,
(group)); directory names are not stripped from URLs.
File-to-Route Mapping
src/pages/page-a/api/submit.func.ts->POST /page-a/api/submitsrc/pages/page-a/feature-a/api/create.func.ts->POST /page-a/feature-a/api/createsrc/pages/page-a/feature-a/api/index.func.ts->POST /page-a/feature-a/api
Verb Naming Conventions
Use consistent action verbs in *.func.ts filenames to keep routes predictable.
- RESTful verbs:
create,update,delete,list,get - Naming pattern:
<verb>-<resource>(kebab-case) - Use singular resource for single-item actions (for example,
create-message) - Use plural resource for collection queries (for example,
list-messages) - Prefer these verbs over synonyms like
add,remove,fetch
Examples:
create-message.func.ts->POST /.../create-messagelist-messages.func.ts->POST /.../list-messages
Verb Semantics
get-<resource>: fetch a single resource (usually by ID or a unique key).list-<resources>: fetch a collection; support filters and pagination.create-<resource>: create one resource.update-<resource>: update an existing resource; treat as partial update unless contract says full replace.delete-<resource>: remove one resource; if implemented as soft delete, document it explicitly.
List Endpoint Conventions
- Use deterministic sorting for
list-*endpoints (for example,created_at DESC, id DESC). - Use one pagination mode per endpoint:
- Offset mode:
page,limit - Cursor mode:
cursor,limit - Set safe limits: default
limitto20, clamp to a max limit (recommended100). - Return pagination metadata in
data(total/page/limitornextCursor). - Validate filter and sort inputs; do not pass client-provided field names directly into SQL.
Naming Anti-Patterns
- Avoid non-standard verbs:
add,remove,fetch,query,do. - Avoid vague action names:
handle-*,process-*,exec-*. - Avoid mixed tense or noun-only names:
created-message,messages.func.ts. - Keep API names domain-oriented; avoid embedding UI event wording in route names.
API Placement Rules
- Put shared page APIs in
page-x/api/ - Put feature-local APIs in
page-x/feature-y/api/ - Keep UI in
components/and APIs inapi/
Protocol Conventions
- Method:
POST - Content-Type:
application/json - Response shape (recommended):
- Success:
{ "ok": true, "data": any, "error": null } - Failure:
{ "ok": false, "data": null, "error": { "code": string, "message": string } }
Prohibited Patterns
1. Do not add path alias routing (for example, auto-mapping /x/api/y to /x/y). 2. Do not add directory semantic rewrite (for example, auto-converting actions to api). 3. Do not use actions/ as the API directory name. Use api/. 4. Do not place *.func.ts under components/.
Minimal Template
src/pages/page-a/api/submit.func.ts:
import { defineFunc } from '@faasjs/func'
export const func = defineFunc(async () => {
return {
accepted: true,
}
})Knex Rules
Use these rules when writing or reviewing FaasJS code with @faasjs/knex.
Core Rules
1. Prefer query-builder APIs (query, useKnex().query, select, insert, update, delete) over raw. 2. Use transaction for multi-step writes that must succeed or fail together. 3. Inside a transaction callback, keep all SQL on the provided trx; do not mix with global query(...). 4. Never interpolate external input into SQL strings. If raw is required, always use bindings. 5. Keep queries explicit:
- use
.select(...)instead of implicit* - use
.first()when expecting one row - add
.where(...)beforeupdateordelete - add deterministic
.orderBy(...)when paginating
Raw SQL (Escape Hatch)
raw is allowed only when query-builder cannot express the SQL clearly, for example:
- vendor-specific SQL features/functions
- performance-sensitive statements that must stay handwritten
- DDL or maintenance scripts
When using raw: 1. Add a one-line comment explaining why query-builder is not enough. 2. Use parameter bindings (? or named bindings), never template strings. 3. Keep raw snippets small and local; avoid large dynamic SQL assembly.
Transactions
- Prefer
transaction(async trx => { ... })from@faasjs/knex. - Use
trxfor all reads/writes in that unit of work. - Let the helper manage commit/rollback automatically.
- If an outer transaction exists, pass it via
options.trxinstead of creating unmanaged nested transactions.
FaasJS Usage
- Configure Knex in
src/faas.yamland letdefineFuncauto-load plugins. - Use
query(...),transaction(...), oruseKnex().query(...)in handlers. - Call
useKnex()directly only when you need a named connection instance. - Do not create ad-hoc
new Knex()in application business code (tests/infrastructure code are exceptions). - Do not call
quit()in request handlers.
Examples
Prefer
# src/faas.yaml
defaults:
plugins:
knex:
config:
client: better-sqlite3
connection:
filename: ./data/app.dbimport { defineFunc } from '@faasjs/func'
import { query, transaction } from '@faasjs/knex'
export const func = defineFunc<{ params: { userId: number } }>(
async ({ event }) => {
const user = await query('users')
.select('id', 'email')
.where({ id: event.params.userId })
.first()
if (!user) throw Error('User not found')
await transaction(async trx => {
await trx('audit_logs').insert({
user_id: user.id,
action: 'login',
})
await trx('users')
.where({ id: user.id })
.update({ last_login_at: new Date() })
})
}
)Avoid
await raw(`UPDATE users SET email='${params.email}' WHERE id='${params.userId}'`)