Office Hours Demo: How To Build a REST directive with RedwoodJS Transformer Directives

How To Build a REST directive with RedwoodJS Transformer Directives

Inspired by Build a REST directive with GraphQL Tools by Jaime Barton which you can watch here:

Video

Jaime shows you how to build a custom @rest directive with GraphQL Tools to resolve data from a JSON API – but, we’ll use Redwood Directives instead to show how you can still implement this feature but not have to go deep into the GraphQL structure.

And … we’ll add a way to fetch a single item from a JSON API … and tests!

Live Demo!!!

:point_right: Live Demo on Netlify

Using a RedwoodJS Directive

Redwood Directives are a powerful feature, supercharging your GraphQL-backed Services.

You can think of directives like “middleware” that let you run reusable code during GraphQL execution to perform tasks like authentication and formatting.

Redwood uses them to make it a snap to protect your API Services from unauthorized access.

Here we call those types of directives Validators.

You can also use them to transform the output of your query result to modify string values, format dates, shield sensitive data, and more! We call those types of directives Transformers.

We’ll be using the Transformer Directive type to imoplemen the @rest directive.

Generating the Directive

  • yarn rw g directive rest --type=transformer
  • rest-directive % yarn workspace api add cross-undici-fetch

Implement the SDL

We’ll use the https://jsonplaceholder.typicode.com JSON api demo to get Photos and Posts.

You can browse the JSON response for https://jsonplaceholder.typicode.com/posts to see how we match the SDL type Post to the example data:

[
  {
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
  },
  {
    "userId": 1,
    "id": 2,
    "title": "qui est esse",
    "body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
  },
  {
    "userId": 1,
    "id": 3,
    "title": "ea molestias quasi exercitationem repellat qui ipsa sit aut",
    "body": "et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut"
  },
  ...
]

For Posts

// api/src/graphql/posts.sdl.ts

export const schema = gql`
  type Post {
    id: Int!
    title: String @uppercase
    body: String
    userId: String
  }

  type Query {
    post(id: Int!): Post
      @rest(url: "https://jsonplaceholder.typicode.com/posts/:id")
      @skipAuth
    posts: [Post]
      @rest(url: "https://jsonplaceholder.typicode.com/posts")
      @skipAuth
  }
`

Posts SDL Code

For Photos

// api/src/graphql/photos.sdl.ts

export const schema = gql`
  type Photo {
    id: Int!
    albumId: Int!
    title: String
    thumbnailUrl: String
    userId: String
  }

  type Query {
    photo(id: Int!): Photo
      @rest(url: "https://jsonplaceholder.typicode.com/photo/:id")
      @skipAuth
    photos: [Photo]
      @rest(url: "https://jsonplaceholder.typicode.com/photos")
      @skipAuth
  }
`

Photos SDL Code

Implement @rest directive

You can see the full @rest transformer directive implementation.

TLDR;

  • It extracts the url set in the directive from it directiveArgs
  • Extracts any query args to be used to replace the named parameters.
  • Replace those params if needed (this is for the fetch post by by query)
  • Construct a url
  • Fetch from this url
  • Return the response

@rest Directive Code

Testing

Yes, you can mock the JSON API response to test that your directive resturns the expected data.

See how the mockRedwoodDirective testing utility let’s you pass in the directiveArtgs and args for the url with a mocked response:

// api/src/directives/rest/rest.test.ts

jest.mock('cross-undici-fetch', () => ({
  fetch: (url) => {
    switch (url) {
      case 'https://example.com':
        return {
          ok: true,
          json: async () => {
            return POSTS_JSON
          },
        }
      case 'https://example.com/1':
        return {
          ok: true,
          json: async () => {
            return POSTS_JSON[0]
          },
        }
    }
  },
}))

// ...

describe('demonstrate use of args for named parameter replacement', () => {
  it('with a url for a single item, returns the json response for that json api url', async () => {
    const mockExecution = mockRedwoodDirective(rest, {
      mockedResolvedValue: '',
      directiveArgs: { url: 'https://example.com/:id' },
      args: { id: 1 },
    })

    await expect(mockExecution()).resolves.toEqual(POSTS_JSON[0])
  })
})

See @rest Directive Unit Test Code

App

Now with these SDL and Directives you can query the RedwoodJS GraphQL API and use RedwoodJS cells to render Posts and photos – live examples!

See the web side code for this.

Ideas to Improve the @rest directive

  • Add headers so can pass api tokens or other Authorization headers
  • Support POST and GET methods
  • Chain with another Transformer directive to reshape the response (transform JSON API data to match a different SDL) (maybe?)

Motivation aka Why not just implement a service?

You may be wondering, why use this @rest directive instead of implementing the same SDL and the implementing a service called posts and post and then in that service, use the same cross-unidici-fetch to fetch the url?

And the answer, is … I’d probably implement this type of feature using a service.

With a service, then a service can call another service (if some other feature needs a few posts to process)… or I could have a serverless function call the posts service to fetch for a RESTful API :).

I might even create a fetch client specifically for the JSON API in api/lib.

But, for very simple REST API fetches one can save quite a bit of code by not having to implement a service method for each endpoint and each get many and get singular.

This Office Hours Example is of a showcase of the power and simplicity of creating Transformer Directives and how one can test them as well.

[Read More[(redwood-office-hours/README.md at main · redwoodjs/redwood-office-hours · GitHub)

2 Likes