Skip to main content
More in Learn

Node Service API Request Handlers

This is a Node library that enables plug and play routing for your Service API when it is using the Node Service API Router.

It provides schemas and handlers that take care of 90% of the work while being highly customizable and totally agnostic of any frameworks.

Installation

With NPM

npm install @crystallize/node-service-api-request-handlers

With Yarn

yarn add @crystallize/node-service-api-request-handlers

Cart Management

The JS API Client already helps you to hydrate products from SKUs or Paths. This handler performs the next step: it hydrates the products and more.

First and as usual, it lets you extend the GraphQL hydration query. Second, it does the price calculation for you.

To use it:

const bodyConvertedRoutes: ValidatingRequestRouting = {
    '/cart': {
        post: {
            schema: cartPayload,
            handler: handleCartRequestPayload,
            args: (context: Koa.Context): CartHydraterArguments => {
                return {
                    perVariant: () => {
                        return {
                            id: true
                        }
                    }
                }
            }
        }
    }
};

And to extend the logic with your own:

const routes: StandardRouting = {
    "/cart": {
        post: {
            handler: async (ctx: Koa.Context) => {                
                const cart = ctx.body as Cart;
                // do whatever you want
                ctx.body = {                    
                    ...cart,
                    hello: "world"
                }
            }
        }
    },
};

That’s it! The heavy lifting is done for you!

Magick Link Authentication

It comes with 2 handlers:

  • handleMagickLinkRegisterPayload
  • handleMagickLinkConfirmationRequestPayload

You can use them in the following way.

Handling the registration / request for a link:

    '/register/email/magicklink': {
        post: {
            schema: magickLinkUserInfosPayload,
            handler: handleMagickLinkRegisterPayload,
            args: (context: Koa.Context): MagickLinkRegisterArguments => {
                return {
                    mailer: createMailer(`${process.env.MAILER_DSN}`),
                    jwtSecret: `${process.env.JWT_SECRET}`,
                    confirmLinkUrl: `http${context.secure ? 's' : ''}://${context.request.host}/confirm/email/magicklink/:token`,
                    subject: "[Crystallize - Boilerplate] - Magic link login",
                    from: "hello@crystallize.com",
                    buildHtml: (request: MagickLinkUserInfosPayload, link: string) => mjml2html(
                        `<mjml>
                        <mj-body>
                        <mj-section>
                            <mj-column>
                            <mj-text>Hi there ${request.email}! Simply follow the link below to login.</mj-text>
                            <mj-button href="${link}" align="left">Click here to login</mj-button>
                            </mj-column>
                        </mj-section>
                        </mj-body>
                    </mjml>`
                    ).html,
                    host: context.request.host
                }
            }
        }
    },

As you can see, the MagickLinkRegisterArguments type lets you inject many things:

  • a mailer: to send the link as well as all the email information: subject, from, and the HTML
  • the jwtSecret: to generate and sign the JTW token
  • the link to confirm the Magick link: confirmLinkPath

You have control over everything while the handler does the heavy lifting.

Then you can leverage the other handler associated with it:

    "/confirm/email/magicklink/:token": {
        get: {
            schema: null,
            handler: handleMagickLinkConfirmationRequestPayload,
            args: (context: Koa.Context): MagickLinkConfirmArguments => {
                return {
                    token: context.params.token,
                    host: context.request.host,
                    jwtSecret: `${process.env.JWT_SECRET}`,
                    backLinkPath: 'https://frontend.app.crystal/checkout?token=:token',
                    setCookie: (name: string, value: string) => {
                        context.cookies.set(name, value, { httpOnly: false, secure: context.secure });
                    }
                }
            }
        },
    },

Of course, if matches the confirmLinkPath passed in the first handler. It is also interesting to note that there is no Schema because there is no body for those requests.

You also need to pass:

  • the jwtSecret: to decode and verify the token
  • provide a link backLinkPath to inform the handler where to redirect the user. (most likely to your frontend)

Once the token is checked and valid, the handler will generate 2 other tokens:

  • a first JWT token that will be saved in the Cookie. This token can then be used to authenticate requests on your service API.
  • a second JWT token that will be passed to the backLinkPath. This token SHOULD NOT be used for authentication, but it is actually a nice format (JWT) to transport non-sensitive information to your frontend.

Orders

These 2 handlers are very simple ones that will check that Orders actually matches the authenticated user after it has fetched the Order(s)

    "/orders": {
        get: {
            schema: null,
            authenticated: true,
            handler: handleOrdersRequestPayload,
            args: (context: Koa.Context): OrdersArguments => {
                return {
                    user: context.user
                }
            }
        }
    },
    "/order/:id": {
        get: {
            schema: null,
            authenticated: true,
            handler: handleOrderRequestPayload,
            args: (context: Koa.Context): OrderArguments => {
                return {
                    user: context.user,
                    orderId: context.params.id
                };
            }
        }
    },

This is a useful endpoint to display the Order(s) to the customer and enforce that this customer is logged in.

People showing thumbs up

Need further assistance?

Ask the Crystallize team or other enthusiasts in our slack community.

Join our slack community