Event-based telefunctions

With REST/GraphQL, API endpoints are:

  • Generic
  • Backend-owned

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

  • Tailored
  • Frontend-owned

This inversion leads to profound improvements in development speed, and we recommend to implement what we call event-based telefunctions.

You may be tempted to create generic telefunctions but this is usually an anti-pattern as explained in the example below.

Example

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

With a RESTful API, the app 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 of implementing and modelling such API.

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
    }
  })
}

With Telefunc, it's the frontend team that owns and is responsible for defining telefunctions.

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

Not only is onMarkAllAsCompleted() more performant than using RESTful API, but it's also faster to implement such tailored telefunction as the need arises while developing the frontend, instead of having to implement an entire RESTful API before even getting started writing the frontend.

That's why, in general, we recommend to implement 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>
      {/* ... */}
    </>
  )
}

While defining telefunctions tailored to serve the needs of a UI commponent, we soon realize that telefunctions are always triggered by some kind of event.

Naming convention

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

    TELEFUNCTION NAME
    =================
    # Generic telefunction:
❌  updateTodoItem
    # Tailored telefunction reacting to a specific user event:
✅  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
✅  components/TodoItem.telefunc.ts
❌  db/todo.telefunc.ts

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

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.

First-time Telefunc users often create generic telefunctions out of the habit of using REST/GraphQL, but defining tailored telefunctions instead is usually the better appraoch.

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.