Hi I'm trying to setup an ApolloApi and I'd like t...
# help
j
Hi I'm trying to setup an ApolloApi and I'd like to have my type defs defined in separate gql files with the resolvers matching on the file names. I've done this before with a normal node application but not with serverless.
Copy code
src/graphql
├── apollo.ts
├── index.ts
├── resolvers
│   ├── getTicket.ts
│   ├── getTickets.ts
│   └── purchase.ts
├── schema.graphql
└── typeDefs
    ├── gql.d.ts
    ├── index.ts
    └── ticket.gql
My
ticket.gql
looks like this
Copy code
type TicketType {
  code: String
  name: String
  description: String!
  cost: Number
  expires: Number
}

type Ticket {
  accountId: String
  ticketId: String
  ticketType: TicketType
  startDate: String
  endDate: String!
  cost: Number
  createdAt: Number
}

input TicketInput {
  accountId: String
  ticketType: TicketType
  startDate: String
}

extend type Query {
  getTickets(): [Ticket]
  getTicket(ticketId: String): Ticket
}

extend type Mutation {
  purchase(request: TicketInput): Ticket
}
This is the guts of my `ApolloApiStack.ts`:
Copy code
this.api = new sst.ApolloApi(this, "ApolloApi", {
      server: {
        bundle: {
          loader: {
            '.gql': 'text'
          },
          esbuildConfig: {
            plugins: "config/esbuild.js",
          },
        },
        handler: "src/graphql/apollo.handler",
      },
    })
The
.build/
has:
Copy code
.build
├── cdk.out
│   ├── asset.3193645abf1dcd8ba8b84cc00bb8cd643c07ec2de06724e0d7478135dd333ab2
│   │   └── index.html
│   ├── asset.8232f53b1494e586db8f965674400246af9ebad94a92aacc2ab86d7165bcc29c
│   │   ├── __entrypoint__.js
│   │   ├── index.d.ts
│   │   └── index.js
│   ├── asset.9c80254908a4e239da4a9e04f71eaf0bbb017308c6bd15699f0425ca4fa0c13b.zip
│   ├── asset.c1f127aea4916491ad407e6278b7fe342685d10e9ac87b4e940e57f72da83433
│   │   ├── index.py
│   │   └── s3-upload.py
│   ├── asset.e9882ab123687399f934da0d45effe675ecc8ce13b40cb946f3e1d6141fe8d68.zip
│   ├── cdk.out
│   ├── jamlen-sst-demo-api.template.json
│   ├── jamlen-sst-demo-auth.template.json
│   ├── jamlen-sst-demo-frontend.template.json
│   ├── jamlen-sst-demo-graphql.template.json
│   ├── jamlen-sst-demo-storage.template.json
│   ├── manifest.json
│   └── tree.json
├── eslint.js
├── lambda-handlers.json
├── lib
│   ├── index.js
│   └── index.js.map
├── run.js
├── src
│   ├── graphql
│   │   ├── apollo.js
│   │   └── apollo.js.map
│   └── tickets
│       ├── delete.js
│       ├── delete.js.map
│       ├── get.js
│       ├── get.js.map
│       ├── my-tickets.js
│       ├── my-tickets.js.map
│       ├── purchase.js
│       ├── purchase.js.map
│       ├── types.js
│       ├── types.js.map
│       ├── update.js
│       └── update.js.map
├── sst-debug.log
├── sst-merged.json
├── sst-start-cache.json
├── static-site-environment-output-keys.json
└── static-site-environment-output-values.json
The error I am getting is
ERROR Error: ENOENT: no such file or directory, scandir '/Users/jamlen/dev/src/github.com/sst-demo/.build/src/graphql/typeDefs'
and I think this is because the typeDefs and resolvers are not included in the
.build
output.
f
Hey @jamlen, can you try something like this:
Copy code
this.api = new sst.ApolloApi(this, "ApolloApi", {
      server: {
        bundle: {
          loader: {
            '.gql': 'text'
          },
          esbuildConfig: {
            plugins: "config/esbuild.js",
          },
          commandHooks: {
            beforeBundling: (inputDir, outputDir) => {
              return [
                `tsc ${inputDir}/src/graphql/resolvers`,
                `cp ${inputDir}/src/graphql/resolvers/*.js ${outputDir}/resolvers`,
                `tsc ${inputDir}/src/graphql/typeDefs`,
                `cp ${inputDir}/src/graphql/typeDefs/*.js ${outputDir}/typeDefs`,
              ];
            },
          },
        },
        handler: "src/graphql/apollo.handler",
      },
    })
Essentially, we are compiling the
resolvers
and
typeDefs
and adding them to the Lambda package manually.
I haven’t tested the code above, but the idea should work.
Give it a try and let me know if it works for you.
Let me also pull in @thdxr and see if he has a better solution for this.
j
I'll give that a go. Ideally I'd like to be able to do
Copy code
new sst.ApolloApi(this, "ApolloApi", {
  server: {
    typeDefs: "src/graphql/typeDefs",
    resolvers: "src/graphql/resolvers",
    handler: "src/graphql/apollo.handler"
  },
})
Or maybe
Copy code
new sst.ApolloApi(this, "ApolloApi", {
  server: {
    handler: {
      path: "src/graphql/apollo.handler",
      typeDefs: "src/graphql/typeDefs",
      resolvers: "src/graphql/resolvers",
    }
  },
})
t
Hey Jamien - I use graphql-codegen to do everything at build time
hm let me think about your file structure
For some reason graphql-codegen doesn't have this built in but I created the following plugin
Copy code
const { printSchemaWithDirectives } = require("@graphql-tools/utils")
const { gql } = require("apollo-server-lambda")
const { stripIgnoredCharacters } = require("graphql")

const print = (schema) => `
  import { gql } from "apollo-server-lambda"
  export const typeDefs = gql\`${schema}\`;
`

module.exports.plugin = (schema) =>
  print(stripIgnoredCharacters(printSchemaWithDirectives(schema)))
and then specified it to create a single typeDefs.ts
Copy code
backend/services/apollo/typeDefs.ts:
    plugins:
      - ./backend/services/apollo/codegen.js
You can specify your schema as a glob pattern
I would strongly recommend doing everything using standard js imports instead of having Apollo server discover / load things at runtime