How to write nodejs APIs using cloudflare workers ES modules?

I tried looking up on the official website and googled about it but I can’t find a complete reference.

I already have a few APIs using the service workers and es module format but I wanted to find a complete set of docs (if available) which helps in creating a complete APIs with correct standards, not sure whether something like that exists or not but if someone can provide help, would be really appreciated.

Check out

1 Like

@anon9246926 I was going through the example you shared but I’m not confident enough with ts and the example is in ts itself, I tried using it in the normal js way but was unable to do so directly.

Here: https://github.com/cloudflare/workers-sdk/blob/main/templates/worker-openapi/src/tasks.ts I tried understanding how data and other params are being passed from a function call: https://github.com/cloudflare/workers-sdk/blob/ade600c4161dfa0bed9fd79d6ecfb4a926553c13/templates/worker-openapi/src/tasks.ts#L45

I’m pretty sure I’m missing something, but is there a straightforward js based example for this?

I’m trying to learn and find a way to write apis as close as i write in plain nodejs.

data in this instance is made up of the schema.parameters in this instance. In the TaskCreate function, data contains the schema.requestBody

Where these details are mentioned? (I’m a little confused)

Have you looked through the itty-router-openapi README.md?

The requestBody is defined the same way as the normal parameters. The validated data will be available inside the body property in the data argument.
Source: https://github.com/cloudflare/itty-router-openapi#request-body-validation

I just read this, thanks for sharing again.

I wanted to understand why there is a need of using a class, and why can’t directly forward an incoming request on a route to a specific function with the complete request object.

For instance:
index.js

import { OpenAPIRouter } from '@cloudflare/itty-router-openapi'

const router = OpenAPIRouter()
router.get('/todos', todos)
router.get('/todos/:id', todoswithid)

async function todos() {
    return new Response("hello")
}

async function todoswithid(request, { params }) {
    return new Response(JSON.stringify(params))
}

// 404 for everything else
router.all('*', () => new Response('Not Found.', { status: 404 }))

export default {
  fetch: router.handle,
}

This /todos/:id should technically return json with the params passed but for some reason this returns nothing.

Also what comes default when passing an incoming request to a function?

I read this request: Request, env: any, context: any, data: Record<string, any> but this is in ts, so for js it will be request, env, context, data

In this case, I’m not sure what exactly comes inside these as when I’m calling the function directly, it is not working, I know I’m missing a lot here but can’t understand directly have a habit of working with expressjs where having (req, res) and middleware makes it a little easier to work with.

If you want to do that, and don’t want to use a class, the use itty-router instead.

Oh okay, I’ve worked with itty-router before, what’s the major difference between these two?

Anyways, I was able to get a decent response from chatgpt, I think it is enough to understand the difference, I’ll try to understand more with the extend class option and see if I can leverage it correctly.

Thanks @anon9246926

Your example represents two different ways of structuring your application and handling routes - by passing a function directly and by using a class that extends a base class. The decision between the two primarily depends on the complexity of your application and your personal preference.

1. **Passing a function directly**: In this case, your functions `TaskList`, `TaskCreate`, `TaskFetch`, `TaskDelete` are passed directly to the router as callbacks. These functions are called when the corresponding HTTP request is made to the specified route. This approach is simple and straightforward, perfect for handling less complex logic or quick prototyping.

2. **Using a class that extends a base class**: Here, `TaskList`, `TaskCreate`, `TaskFetch`, `TaskDelete` are classes that extend `OpenAPIRoute`. Each class represents a route and contains both the schema for the route and a `handle` method which is called when a request is made to that route.

    - **Encapsulation**: Each class encapsulates the logic for a particular route, keeping the code organized and easy to reason about.
    - **Reuse**: If there's common logic in the `OpenAPIRoute` base class, it's automatically available to all subclasses. This reduces code duplication.
    - **Inheritance**: You can further extend these classes to create more specific routes, if needed.
    - **Ease of Testing**: The class structure can make unit testing easier since you can instantiate a class and test its methods independently.

In terms of functionality, both ways should achieve the same result. The main difference lies in the organization, maintainability, and scalability of your code. If your routing logic is complex, you might find it beneficial to encapsulate that logic inside classes. Conversely, if your routing logic is fairly straightforward, passing a function directly might be a simpler and cleaner solution.

The itty-router-openapi answers that

itty-router-openapi is built on top of itty-router and extends some of its core features, such as adding class-based endpoints. It also provides a simple and iterative path for migrating from old applications based on itty-router.

It also features an OpenAPI 3 schema generator

Thanks for pointing this @anon9246926

Also, I think if I can use other validation libraries for input params or incoming data then I think it will be okay to not use the built-in function in the new itty-router-openap:

  static schema = {
    tags: ['ToDo'],
    summary: 'Create a new Todo',
    requestBody: {
      title: String,
      description: new Str({ required: false }),
      type: new Enumeration({
        values: {
          nextWeek: 'nextWeek',
          nextMonth: 'nextMonth',
        }
      })
    },
    responses: {
      '200': {
        schema: {
          todo: {
            id: 123,
            title: 'My title',
          },
        },
      },
    },
  }

similar to: https://github.com/ashishjullia/easyin-backend-NodeJS/blob/0d5ea4dfdcf378b0f8c398bd9507955f3755abd2/routes/userRoute.js#L6

Now I just have to find a library that is supported for cf worker as express-validator is not supported.

Now, I think it goes by choice whether which way I want to go with (If I understood this correctly)

I don’t believe that is possible. Best open an issue on the repository so the maintainer can advise.

Sure I’ll do that once I’m done with testing these: GitHub - cloudflare/itty-router-openapi: OpenAPI 3 schema generator and validator for Cloudflare Workers

Looks promising.

@anon9246926 I was able to understand the strict validations here and why schemas is defined at class level and in turn why each class goes to a single function binding but is this a best practice? As in what if the API and routes increase by say 50 then if following this scheme then there will be 50 classes with 50 functions and 50 schemas to each of these.

Am I missing something?

I think one way would be to use a single class with multiple functions and then export that class but in this case, the schema should be written in a way that it satisfies validations for each of the routes (50 in this example), I don’t think that this is feasible as well.

Please correct me If I’m wrong here.

I don’t believe so.

If you have 50 endpoints, I would assume they all accomplish 50 different things. Ergo each would likely have different inputs and responses. Ergo they need different schema.

The missing part for me was schema and parameters because I never worked with API (openAPI standards, I like how /docs and /redocs are available right out of the box, beautiful) which self-documents itself, now I understood and was able to write my first route with Path and was able to successfully get the validation part working (nice, no need to import extra libraries for validation checks).

Thanks @anon9246926 , for now I believe my problem is solved, I’ll play more with creating more routes and other things.

One more question, when I publish my API, will the /docs and /redocs will be automatically available to end users? I think they will be available but is it a good idea considering the security and other things? (don’t know much about this, so asking)

Yes, they are automatically available.

You can disable both paths as outlined itty-router-openapi Options API.

Depends on whether you are making a public or private API. Documentation definitely helps in either case.

1 Like

Great @anon9246926 thanks a lot for all the details, I learned a few good things today :+1:

1 Like

This topic was automatically closed 3 days after the last reply. New replies are no longer allowed.