javascripttypescriptcloudflarecloudflare-workersitty-router

itty-router and TypeScript with middleware


I'm trying to create a CloudFlare Worker using the itty-router. Following the guide on TypeScript we have to define the TypeScript types as in the code below.

The code works fine but there's a TypeScript error in the part async...:

Argument of type '({ content }: IRequest, env: Args[0], ctx: Args[1]) => Promise<{ success: boolean; status: number; data: { [key: string]: any; }; error: null; } | { success: boolean; status: number; data: null; error: any; }>' is not assignable to parameter of type 'RequestHandler<IRequest, []>'.
   Target signature provides too few arguments. Expected 3 or more, but got 1.ts(2345)
import { createClient } from '@supabase/supabase-js'
import { AutoRouter, IRequest, withContent } from 'itty-router'

interface Env {
    SUPABASE_URL: string
    SUPABASE_KEY: string
}
type CFArgs = [Env, ExecutionContext]

const router = AutoRouter<IRequest, CFArgs>()

router.post(
    '/clients',
    withContent,
    async ({ content }, env, ctx) => {
        try {
            const body = content as { [key: string]: any }
            const newClient = { ...body }

            const supabase = createClient(env.SUPABASE_URL, env.SUPABASE_KEY)
            const { data, error } = await supabase.from('clients').insert(newClient)

            if (error) throw new Error(`Supabase error: ${error.message}`)

            return {
                success: true,
                status: 200,
                data: newClient,
                error: null,
            }
        } catch (error: any) {
            return { success: false, status: 500, data: null, error: error.message }
        }
    }
)

Solution

  • There are a couple of ways around this, but the easiest is to redeclare the generics on the route itself:

    const router = AutoRouter<IRequest, CFArgs>()
    
    router.post<IRequest, CFArgs>( // add the generics here
        '/clients',
        withContent,
        async ({ content }, env, ctx) => {
          // handler internals
        }
    )
    

    Additionally, should you ever try to abstract these into an external handler, you'll want to type them at the handler level before you can add them into the route flow (to avoid TS errors). This will make more sense below:

    const handler1 = async ({ content }, env, ctx) => {} // BAD
    const handler2 = async ({ content }: IRequest, env: Env, ctx: ExecutionContext) => {} // GOOD
    const handler3: RequestHandler<IRequest, CFArgs> = async ({ content }, env, ctx) => {} // GOOD
    
    router.post<IRequest, CFArgs>(
        '/clients',
        withContent,
        handler1, // TS error
        handler2, // this is fine
        handler3, // this is fine
        async ({ content }, env, ctx) => {
          // handler internals
        }
    )