honojs / hono

Web framework built on Web Standards
https://hono.dev
MIT License
18.52k stars 521 forks source link

Export multiple route's type #1031

Open hisamafahri opened 1 year ago

hisamafahri commented 1 year ago

Hi, thank you for the amazing work for HonoJS so far.

I have a question, how do I export types for multiple route using one export declaration? Because I'd like to import single type in my client (const client = hc<ApiType>("/api")).

For example:

const app = new Hono().basePath("/api");

export const helloRoute = app.get("/hello", (c) => hello());
export const greetRoute = app.get("/greet", (c) => greet());
export type ApiType = typeof app; // <- How do I export all routes at once?

export default handle(app);

Currently my workaround is this:

const app = new Hono().basePath("/api");

export const helloRoute = app.get("/hello", (c) => hello());
export const greetRoute = app.get("/greet", (c) => greet());
export type ApiType = typeof helloRoute | typeof greetRoute;

export default handle(app);

But this approach requires me to add another union declaration for each routes that I have.

yusukebe commented 1 year ago

Hi @hisamafahri !

Thanks for raising the issue.

You are right! Unfortunately, I only know the way that uses Union to export route types as you wrote. I am looking for a smarter way to export multiple routes, too.

For now, let's leave this Issue open.

mikestopcontinues commented 1 year ago

Chaining produces better results. app = new Hono().get().get(). However, it falls apart when using .route() to bundle endpoints and middleware. Especially if you do .route('/:userId', new Hono().get('/key/:keyId')). Using validator() ware also causes different results (at least within .route() calls).

See the tests for what's currently supported.

andreataglia commented 9 months ago

any update on here?

yusukebe commented 9 months ago

@andreataglia

No. Please use Union:

export type ApiType = typeof helloRoute | typeof greetRoute;
zysam commented 9 months ago

if write that(not chaining), the app type doesn't work;

const app = new Hono()
app.use();
app.use();

export AppType = typeof app
// ...

you want type-safe, only use chaining style, but the code style likes tRPC;

// hono style
const app = new Hono().use().use() // ...

// tRPC style
const route = t.route({
list: () => {}
//...
})

I think, Have any plan which can generate route type(or other app's meta) for client(or plugin) in build time? so we can choice any free style to write routes and keep type-safe.

yusukebe commented 9 months ago

Have any plan which can generate route type(or other app's meta) for client(or plugin) in build time?

We currently don't have a plan for this, but if you know of any good example applications that can generate route types statically at build time, please let us know. We can refer to it.

zjx20 commented 5 months ago

I attempted to write a helper class to assist in collecting type information. While I think it's better than manually writing union types, it still feels a bit awkward. I hope someone can propose a better idea.

samuelgoldenbaum commented 3 weeks ago

While this ticket is being considered, I eventually shifted to tRPC as an option which works nicely with:

// or use mergeRouters()
export const appRouter = router({
  ...originRouter(),
  ...franchiseRouter()
})

export type AppRouter = typeof appRouter

Sample:

@server/trpcs.ts

import { initTRPC } from '@trpc/server'

const t = initTRPC.create()

export const router = t.router
export const publicProcedure = t.procedure

@server/router.ts

import { router } from '@server/trpc'
import { originRouter } from '@server/modules/origins'
import { franchiseRouter } from '@server/modules/franchises'

export const appRouter = router({
  ...originRouter(),
  ...franchiseRouter()
})

export type AppRouter = typeof appRouter

@server/main.ts

import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { trpcServer } from '@hono/trpc-server' // Deno 'npm:@hono/trpc-server'
import { appRouter } from './router'

const app = new Hono()

app.use(cors())

app.use(
  '/trpc/*',
  trpcServer({
    router: appRouter
  })
)

export default app

@server/modules/origins/index.ts

import { z } from 'zod'
import { publicProcedure, router } from '@server/trpc'
import { faker } from '@faker-js/faker'
import { v4 as uuid } from 'uuid'
import { TRPCError } from '@trpc/server'
import { on } from 'events'
import { eventEmitter } from '@server/events'
import { CREATE_ORIGIN } from '@server/modules/origins/types'

const OriginSchema = z.object({
  id: z.string().uuid(),
  name: z.string().max(64),
  year: z.number().int().min(1900).max(new Date().getFullYear()),
  updatedAt: z.string()
})
export type Origin = z.infer<typeof OriginSchema>

const data: Array<Origin> = [
  {
    id: uuid(),
    name: faker.company.name(),
    year: faker.date.past({ years: 1 }).getFullYear(),
    updatedAt: new Date().toISOString()
  }
]

const CreateOriginSchema = OriginSchema.omit({ id: true, updatedAt: true })
export type CreateOriginDto = z.infer<typeof CreateOriginSchema>

const getSchema = z.object({
  id: z.string().uuid()
})

export const originRouter = () => {
  return {
    origins: router({
      create: publicProcedure
        .input(CreateOriginSchema)
        .mutation(async ({ input }) => {
          const origin: Origin = OriginSchema.parse({
            id: uuid(),
            name: input.name,
            year: input.year,
            updatedAt: new Date().toISOString()
          }) satisfies Origin

          data.push(origin)

          eventEmitter.emit(CREATE_ORIGIN, origin)

          return origin
        }),
      getAll: publicProcedure.query(() => {
        return data
      }),
      get: publicProcedure.input(getSchema).query(async ({ input }) => {
        const origin = data.find((origin) => origin.id === input.id)
        if (origin == null) {
          throw new TRPCError({
            code: 'NOT_FOUND'
          })
        }

        return origin
      }),
      onCreate: publicProcedure.subscription(async function* () {
        for await (const [data] of on(eventEmitter, CREATE_ORIGIN)) {
          yield data as Origin
        }
      })
    })
  }
}