Event-based telefunctions

With REST/GraphQL, API endpoints are:

  • Generic
  • Backend-owned (it's the backend team/development that creates and defines API endpoints)

With Telefunc, it's the exact opposite and telefunctions are usually:

  • Tailored
  • Frontend-owned (it's the frontend team/development that creates and defines telefunctions)

This inversion leads to a significantly faster development speed.

You may be tempted to create generic telefunctions out of the habit of using REST/GraphQL, but this is usually an anti-pattern as explained in the example below. Instead, we recommend to implement what we call event-based telefunctions.

Example

Let's imagine a to-do list app that wants to implement a new feature "mark all tasks as completed".

With a RESTful API, the app typically does these requests:

HTTP VERB       HTTP URL                                      HTTP BODY PAYLOAD
=========       =========================================     =====================
GET             https://api.todo.com/task?completed=false     ∅
POST            https://api.todo.com/task/42                  { "completed": true }
POST            https://api.todo.com/task/1337                { "completed": true }
POST            https://api.todo.com/task/7                   { "completed": true }

It's usually the backend team that owns and is responsible for implementing and modelling such API.

This is quite inefficient as the app does N+1 requests, where N is the number of tasks that needs to be updated.

With Telefunc, we can do this instead:

// TodoList.telefunc.ts
// Environment: Server

export async function onMarkAllAsCompleted() {
  // With SQL
  await sql('UPDATE tasks SET completed = true WHERE completed = false')

  // With an ORM
  const updateUser = await Tasks.update({
    where: {
      completed: false
    },
    data: {
      completed: true
    }
  })
}

It's usually the frontend team that owns and is responsible for defining such telefunction.

The key difference is that our telefunction onMarkAllAsCompleted() is created specifically to serve the needs of our component TodoList.tsx, whereas a RESTful API is a set of generic endpoints that are agnostic to our frontend.

Not only is it more performant, but it's also faster to implement tailored telefunctions as the need arises – instead of having to implement an entire RESTful API before even getting started with the development of the frontend.

Thus, in general, we recommend implementing telefunctions that are tailored instead of generic.

We named our telefunction "onMarkAllAsCompleted" (instead of "markAllAsCompleted") because it's triggered by a user event:

// TodoList.tsx
// Environment: Browser

import { onMarkAllAsCompleted } from './TodoList.telefunc.ts'

function TodoList() {
  return (
    <>
      <button
        onClick={async () => await onMarkAllAsCompleted()}
      >
        Mark all as completed
      </button>
      {/* ... */}
    </>
  )
}

When defining telefunctions tailored to serve the needs of UI components, we soon realize that telefunctions are usually triggered by a user event.

Naming convention

By default, we recommend to prefix all telefunction names with "on":

    TELEFUNCTION NAME
    =================
# Generic telefunction:
❌  updateTodoItem
# Tailored telefunctions reacting to user events:
✅  onTodoItemTextUpdate
✅  onTodoItemCompleteToggle

    # Data-fetching telefunction
❌  loadData
✅  onLoad
✅  onPagination
✅  onInfiniteScroll

We also recommend to collocate .telefunc.js files with UI components:

    FILES
    ===============================
    # UI Component
➡️  components/TodoItem.tsx
    # Telefunction
✅  components/TodoItem.telefunc.ts
❌  db/todo.telefunc.ts

    # UI Component
➡️  pages/edit/Page.vue
    # Telefunction
✅  pages/edit/Page.telefunc.js
❌  pages/edit/all.telefunc.js

Users using Telefunc for the first time often create generic telefunctions out of the habit of using REST/GraphQL, but defining tailored telefunctions instead is usually the better appraoch as explained above.

Telefunc displays a warning whenever the naming convention isn't followed. We can opt-out with config.disableNamingConvention, but we recommend that you carefully consider whether you really want to opt-out.

Exceptions

As explained above, we recommend implementing telefunctions that are tailored instead of generic, but there are exceptions.

The most common exception is if our telefunctions are consumed not only by one client but by several clients, and if these clients are deployed independently of each other. In that case it can make sense to define few generic telefunctions covering all clients, instead of defining different telefunctions for each client. An alternative is to deploy one Telefunc server per client; that way we can preserve the fast development speed of tailored telefunctions.