Rate Limiting (Manual)
Prevent abuse on public endpoints by splitting the flow into two actions -- one to check the limit, one to do the work. You check first, then submit.
Note: Teenybase doesn't have built-in rate limiting yet. This is a manual workaround. For production use, consider adding rate limiting in a custom Hono middleware (see Existing Hono Projects).
Config
typescript
// Add a request_log table
{
name: 'request_log',
autoSetUid: true,
fields: [
...baseFields,
{ name: 'action_name', type: 'text', sqlType: 'text', notNull: true },
{ name: 'ip_hash', type: 'text', sqlType: 'text', notNull: true },
],
triggers: [createdTrigger],
indexes: [
{ fields: 'action_name' },
{ fields: 'ip_hash' },
],
extensions: [{
name: 'rules',
listRule: null, // no direct access
viewRule: null,
createRule: null,
updateRule: null,
deleteRule: null,
} as TableRulesExtensionData],
},
// Then in your actions:
actions: [
{
// Action 1: Submit form + log the request
name: 'submit_form',
params: { email: 'string', message: 'string', ip_hash: 'string' },
applyTableRules: false,
sql: [
// Log this request
{
type: 'INSERT',
table: 'request_log',
values: {
action_name: sql`'submit_form'`,
ip_hash: sql`{:ip_hash}`,
},
},
// Do the actual work
{
type: 'INSERT',
table: 'submissions',
values: {
email: sql`{:email}`,
message: sql`{:message}`,
},
returning: ['id'],
},
],
},
{
// Action 2: Check rate limit (call this first from your client)
name: 'check_rate_limit',
params: { action_name: 'string', ip_hash: 'string' },
applyTableRules: false,
sql: {
type: 'SELECT',
table: 'request_log',
selects: [{ q: 'COUNT(*)', as: 'count' }],
where: sql`action_name = {:action_name} AND ip_hash = {:ip_hash} AND created > datetime('now', '-1 hour')`,
},
},
]API calls
javascript
// Client-side: check rate limit, then submit
const checkRes = await fetch('/api/v1/action/check_rate_limit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action_name: 'submit_form', ip_hash: hashedIp }),
});
const [[{ count }]] = await checkRes.json();
if (count >= 5) {
alert('Too many requests. Please try again later.');
} else {
await fetch('/api/v1/action/submit_form', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, message, ip_hash: hashedIp }),
});
}Caveat: This is not bulletproof -- a determined user can skip the check and call submit_form directly. For server-side enforcement, add rate limiting in a custom Hono middleware before the teenybase routes.