Using redwood to make an api to be used by my users software

Howdy folks,

I have a project where I will be making some software that requires my users to directly access my API from their software. E.g like you may use a transaction email services API or Stripes API.

I love using redwoodjs for building apps, but I am not sure if it is a good fit for this use case.

Ideally, my user can generate an access token from my frontend and then use this to authenticate with the API. Using redwood I figured I had two approaches.

  1. Let them access my Graphql API and do something with a custom directive to check the validity of a token that’s passed in with an auth header (I’m just guessing this is possible)

  2. Use redwood functions to create a series of REST endpoints and deal with the auth over there.

I have no idea if my users have a preference for working with rest or Graphql at this stage. If I was going to guess I would say REST

Does anyone have thoughts on these approaches? Got a better idea? Think redwood is not the best for this? Love to hear your thoughts. Thanks.

Okay been pondering this for a little while.

A possible solution I have come up with is to modify my server file to include a traditional API route like so.

import { createServer } from '@redwoodjs/api-server'

import { logger } from 'src/lib/logger'


const config = {
  fastifyServerOptions: {
    requestTimeout: 15_000,
  },
  logger: {
    level: process.env.NODE_ENV === 'development' ? 'debug' : 'warn',
  },
}

async function main() {
  const server = await createServer({
    ...config,
  })

  server.route({
    method: 'GET',
    url: '/api/ping',
    handler: () => {
      return 'pong'
    },
  })

  console.log(server.printRoutes())

  await server.start()
}

main()

So my theory is that I get to enjoy all the redwood conveniences for making my app and get to provide a traditional rest API endpoint that will be able to use my existing services.

Does anyone have opinions on this? is it a bad idea?

The reality is my option if I can’t use Redwood is I am going to end up making express/fastify backend and since Redwood is fastify, the above logic is making some sense to me

Hey @shansmith01

Thanks for talking out loud here, this is great for the community.

These aren’t things I’ve personally tried, but I know that people have.

One option would be generate REST API endpoints using GitHub - Urigo/SOFA: The best way to create REST APIs - Generate RESTful APIs from your GraphQL Server

Now even if you end up decided you want a second server, fastify, express, whatever - you could use your redwood graphql schema without needing to duplicate logic everywhere!

Let us know if you decide to experiment with this, keen to know how you get on!

FYI. @shansmith01 and I have exchange some examples to get SOFA running here: Explore REST API from GraphQL Schema using Sofa · Issue #5480 · redwoodjs/redwood · GitHub


Hi @shansmith01 - I finally got around to testing today and here’s how I configured my GraphQL handler:

Be sure to install the plugin:

yarn workspace api add @graphql-yoga/plugin-sofa
import { useSofa } from '@graphql-yoga/plugin-sofa'

import { createGraphQLHandler } from '@redwoodjs/graphql-server'

import directives from 'src/directives/**/*.{js,ts}'
import sdls from 'src/graphql/**/*.sdl.{js,ts}'
import services from 'src/services/**/*.{js,ts}'

import { db } from 'src/lib/db'
import { logger } from 'src/lib/logger'

export const handler = createGraphQLHandler({
  loggerConfig: { logger, options: {} },
  directives,
  sdls,
  services,
  extraPlugins: [
    useSofa({
      basePath: '/graphql',
      swaggerUI: {
        endpoint: '/swagger',
      },
    }),
  ],
  onException: () => {
    // Disconnect from your database with an unhandled exception.
    db.$disconnect()
  },
})

Then, visit http://localhost:8911/graphql/swagger

Note: I scaffolded a Country model with 4 seeded countries.

And curl:

~ % curl -X 'GET' \
  'http://localhost:8911/graphql/countries' \
  -H 'accept: application/json'
[{"id":1,"name":"Sweden","code":"SE"},{"id":2,"name":"Finland","code":"FI"},{"id":3,"name":"USA","code":"US"},{"id":4,"name":"Canada","code":"CA"}]%
~ %

Note: I think there are some other ways to configure to get an endpoint you may prefer.

For example, you can leave your graphql.ts function as is, and the duplicate it in functions as rest.ts, and then:

import { useSofa } from '@graphql-yoga/plugin-sofa'

import { createGraphQLHandler } from '@redwoodjs/graphql-server'

import directives from 'src/directives/**/*.{js,ts}'
import sdls from 'src/graphql/**/*.sdl.{js,ts}'
import services from 'src/services/**/*.{js,ts}'

import { db } from 'src/lib/db'
import { logger } from 'src/lib/logger'

export const handler = createGraphQLHandler({
  loggerConfig: { logger, options: {} },
  directives,
  sdls,
  services,
  graphiQLEndpoint: 'rest',
  extraPlugins: [
    useSofa({
      basePath: '/rest/api',
      swaggerUI: {
        endpoint: '/swagger',
      },
    }),
  ],
  onException: () => {
    // Disconnect from your database with an unhandled exception.
    db.$disconnect()
  },
})
```a

Then you can curl at:

```bash
~ % curl -X 'GET' \
  'http://localhost:8911/rest/api/countries' \
  -H 'accept: application/json'
[{"id":1,"name":"Sweden","code":"SE"},{"id":2,"name":"Finland","code":"FI"},{"id":3,"name":"USA","code":"US"},{"id":4,"name":"Canada","code":"CA"}]%
~ %

If you want status codes for auth, we haven’t set those in RW by default for Authentication errors etc, but, in your service you can throw

  throw new RedwoodGraphQLError('This is a custom auth message', {
    http: {
      status: 401,
    },
  })

Might just need some added extension info in redwood/packages/graphql-server/src/errors.ts at e798075ca6e81655f8ae7869664004ccf94633d2 · redwoodjs/redwood · GitHub but that might break the ApolloClient error handling.

If perhaps you have dedicated services for REST endpoints, probably can just throw errors you need for 401 or 4xx as needed.

Hope this helps!