Docs
Core Concepts
Decorators

Decorators

Decorators are a a powerful feature of Pylon, that allow you to secure, modifiy or extend your service functions.

Overview

Pylon provides a decorator factory createDecorator that allows you to create custom decorators for your service functions. Decorators can be used to add additional functionality to your service functions, such as authentication, logging, or other concerns.

Creating a Decorator

To create a decorator, you can use the createDecorator function from the @getcronit/pylon package. This function takes a callback function as an argument, which will be executed when the decorator is applied to a service function.

import {app, createDecorator} from '@getcronit/pylon'
 
const log = createDecorator(async () => {
  console.log('Executing service function')
})
 
class Foo {
  @log
  foo = async () => {
    return 'foo'
  }
}
 
export default app

In this example, the log decorator logs a message when the foo service function is executed.

Applying a Decorator

To apply a decorator to a service function, you can use the decorator syntax provided by TypeScript. Decorators can be applied to class methods, static methods, or properties.

import {app, createDecorator} from '@getcronit/pylon'
 
const log = createDecorator(async () => {
  console.log('Executing service function')
})
 
class Foo {
  @log
  foo = async () => {
    return 'foo'
  }
 
  @log
  static bar = 'bar'
 
  @log
  static baz() {
    return 'baz'
  }
 
  @log
  static qux = () => {
    return 'qux'
  }
 
  @log
  bar = 'bar'
 
  @log
  baz() {
    return 'baz'
  }
 
  @log
  qux = () => {
    return 'qux'
  }
}
 
const foo = new Foo()
 
export const graphql = {
  Query: {
    instanceFoo: foo.foo,
    staticBar: Foo.bar,
    staticBaz: Foo.baz,
    staticQux: Foo.qux,
    instanceBar: foo.bar,
    instanceBaz: foo.baz,
    instanceQux: foo.qux
  }
}
 
export default app

You can also apply decorators by calling the decorator function directly.

import {app, createDecorator} from '@getcronit/pylon'
 
const log = createDecorator(async () => {
  console.log('Executing service function')
})
 
export const graphql = {
  Query: {
    welcome: log((firstName: string, lastName: string) => {
      return `Hello ${firstName} ${lastName}`
    })
  }
}

Arguments Validation

You can use decorators to validate the arguments of a service function. This can be useful for enforcing e.g. password requirements.

import {app, createDecorator, ServiceError} from '@getcronit/pylon'
 
const validatePassword = createDecorator(
  async (username: string, password: string) => {
    if (password.length < 8) {
      throw new ServiceError('Password must be at least 8 characters long', {
        code: 'INVALID_PASSWORD',
        statusCode: 400
      })
    }
  }
)
 
export const graphql = {
  Query: {
    hello: 'Hello World!'
  },
  Mutation: {
    login: validatePassword((username: string, password: string) => {
      return `Welcome, ${username}!`
    })
  }
}
 
export default app

In this example, the validatePassword decorator checks if the password provided to the login service function is at least 8 characters long. If the password is too short, a ServiceError is thrown with a status code of 400.

Hono Middleware as Decorators

All Hono middleware functions can be used inside decorators. This allows you to apply middleware to service functions, instead of routes. For example, you can use the basicAuth middleware to require authentication for a service function.

import {app, createDecorator, getContext} from '@getcronit/pylon'
import {basicAuth} from 'hono/basic-auth'
 
const authMiddleware = basicAuth({
  username: 'admin',
  password: 'password'
})
 
const requireBasicAuth = createDecorator(async () => {
  const ctx = getContext()
 
  await authMiddleware(ctx, async () => {})
})
 
export const graphql = {
  Query: {
    secure: requireBasicAuth(() => {
      return 'You are authenticated!'
    })
  }
}
 
app.get('/', authMiddleware, c => {
  return new Response('Hello World')
})
 
export default app

In this example, the requireBasicAuth decorator applies the basicAuth middleware to the secure service function, requiring the user to provide a username and password to access the function. Now you can navigate to http://localhost:3000 and you will be prompted to enter a username and password. Afterwards you can access the secure service function.

Take a look at the Hono documentation (opens in a new tab) for more information on available middleware functions.

Decorators as Middleware

You can also use decorators as middleware functions for routes. This allows you to apply the same middleware to multiple routes.

import {app, createDecorator, getContext} from '@getcronit/pylon'
import {basicAuth} from 'hono/basic-auth'
 
const requireBasicAuth = createDecorator(async () => {
  const ctx = getContext()
 
  await basicAuth({
    username: 'admin',
    password: 'password'
  })(ctx, async () => {})
})
 
export const graphql = {
  Query: {
    hello: 'Hello World!'
  }
}
 
app.get(
  '/',
  requireBasicAuth(async (c, next) => {
    return new Response('Hello World')
  })
)

In this example, the requireBasicAuth decorator is used as middleware for the / route, requiring the user to provide a username and password to access the route.