graphql-framework-experiment
graphql-framework-experiment copied to clipboard
Serverless mode
Perceived Problem
- serverless is a popular kind of deployment; nextjs, vercel, lambda, serverless frmework, google cloud functions, kubernetes functions, edge compute, apollo-micro, ...
- nextjs integration #648 needs serverless
Ideas / Proposed Solution(s)
-
Nexus needs to expose a "request event" handler. This is what the host server platform will call.
-
Many Node http server APIs have request handlers with request and response parameters. However it seems a lot more intuitive to have request as the input and response as asynchronous output.
interface NexusServer { handle: (req: NexusServerRequest) => Promise<NexusServerResponse> } -
...But what does a user do when they want to code against their deployment platform of choice. For example from a comment in the nextjs integration issue:
/ pages/api/graphql.ts mport Cors from 'cors' mport { server, use } from 'nexus' mport { nextjs } from 'nexus-plugin-nextjs' se(nextjs()) xport default server.handler / Initializing the cors middleware onst cors = Cors({ methods: ['GET', 'HEAD'], ) / Helper method to wait for a middleware to execute before continuing / And to throw an error when an error happens in a middleware unction runMiddleware(req, res, fn) { return new Promise((resolve, reject) => { fn(req, res, result => { if (result instanceof Error) { return reject(result) } return resolve(result) }) }) sync function handler(req, res) { // Run the middleware await runMiddleware(req, res, cors) // Rest of the API logic server.handle(req, res) // <---- nexus handler here xport default handler- https://nextjs.org/docs/api-routes/api-middlewares
- We could introduce
server.platformHandleorserver.handle.fromPlatformbut it is unclear why we need the indirection, why we need both. It seems right now that we should just makeserver.handlebe that.
-
The request handler should be exported for use by the host platform. Example with nextjs (with vercel):
import { server } from 'nexus' const handleRequest = server.handle export default handleRequestThe inability to export an import directly seems bad. But one could say either way is boilerplate and wht's really bad is doing any of that in the first place.
-
several things should change in nexus between serverful and serverless modes
server.startserver.stopAPIs should only appear in serverful modeserverjsDoc should reflect the current server mode- Nexus dev mode; In serverful mode it should run the app server (what it does today). In serverless mode it should emulate a serverless platform and call the app's request handler
server.expressAPI should not appear in serverless mode. Express should not even be in the final built bundle.- server settings;
portandhostshould only appear in serverful modepathis for configuring the graphql endpoint path. The concept of path control applies to both serverful and serverless systems but its configuration/mental model is very different. In serverless the path may be configuration within terraform, yaml, cloud formation, file name/location (nextjs), etc. To the extend that Nexus can automate this thepathconfiguration makes sense in serverless mode, however it stops making sense once a user needs to manually configure path in another system.- In serverless mode, the port used by dev mode serverless platform emulation should probably be configurable by the user. How should that work? Re-purpose
port?settings.server.platform.port?settings.dev.server.platform.port? Conceptually, configuring the platform that runs the app in the app makes no sense, so...package.json#nexus.serverless.port? Or the conceptual nonsense is acceptable for the simplicity it gives for dev and transition between serverless/serverful? - In serverless mode,
playground.pathhas the same issues as described forpath. Additionally, if users expect to deploy playground in serverless mode, it raises the question of multiple endpoints, which in serverless, each constitutes its own app.
-
Some things should not change between serverful and serverless modes
-
Server middleware #523 should remain abstracted from the host platform so that plugins can provide server middleware without concern for the server in use.
This is non-trivial because certain http features are simply not possible on certain serverless platforms. How can a plugin author design their plugin for maximum portability, needing some server features, but not knowing if the plugin will be used in an environment that has that capability?
The design problems here are not unlike prisma which abstracts different databases.
Maybe some kind of capability modelling/matching system will need to be designed.
This is a whole topic unto itself.
-
-
Some of the points above (
settings.server.path, server middleware, ...) make pretty clear that a serverless mode is not enough, but a sererless platform mode is needed, wherein the particulars of a server platform are taken into account, adapted for.
References
- https://github.com/graphql-nexus/nexus/issues/648#issuecomment-620205954
- https://github.com/graphql-nexus/nexus/issues/295
- https://nextjs.org/docs/api-routes/api-middlewares
Some additional thoughts came up with @Weakky today while sprint planning.
-
Going between serverless and serverful should not be mutually exclusive. This is a significant change in our thinking.
-
How do we talk about this? Is there one app with zero or more lambdas and zero or one server? Or is there one project with zero or more serverless apps and zero or one serverful app? Does a project have an app or does app/project mean the same thing? Is it that a project has multiple apps or that an app has multiple entrypoints? "Entrypoints" instead of "apps"? ...
-
In dev mode, a serverless platform emulator would run the lambda entrypoints, and the serverful entrypoint. But reflection on each could lead to different typegen. Typegen is global. There would be conflicts.
@Weakky maybe we can look into tsconfig paths to find way to have each lambda consume only its respective typegen...
-
Build would needs to run per entrypoint. And if we cannot find solution to global typegen (see above point), then serially.
-
Nexus will need to create a bundle per lambda. This does overlap with what e.g. Next.js / Vercel provide, but we think that's ok. Just like we wanted a bundle/tree-shake step #119 but again Next.JS also provides that. The point is integrations will always have some feature overlap with pure Nexus, and Nexus should just be as much as possible flexible enough to turn off the overlapping parts.
-
If there is a convention for serverless then we probably need a convention for serverful, e.g.
server.tsbecomes the serverful entrypoint for the app. For example in the following there would be three entrypoints in the app.erver.ts <-- serverful entrypoint ambdas/ graphql.ts <-- serverless entrypoint 1 playground.ts <-- serverless entrypoint 2 -
Re bundles, given the above example, the build result would be e.g.:
ode_modules/ .build/ server.js <-- (self-contained bundle) lambdas/ graphql.js <-- (self-contained bundle) playground.js <-- (self-contained bundle) -
Plugins could tailor further to platform needs, e.g.
nexus-plugin-aws-lambdacould zip the lambdas etc. -
Lambdas would use file paths to infer URL paths
-
All the different entrypoints, if they constitute their own apps, will need their own app state. Changing the schema or changing the settings should apply to a specific entrypoint, not all. Of course the classic way to do this is build up state at the module level from stateless imports, then export/import everywhere to build up a graph of shared module deps where needed, etc. But that's really against the Nexus way (today). How does this problem look to Nexus.
-
I think ☝️ is one of the biggest fundamental issues, so here's a point just to point to that point 🤦 😛
-
Multiple serverless entrypoints in an app look an aweful lot like multiple serverful apps in a monorepo. ...? It feels like we can generalize the system further to accept zero-or-more serverless AND serverful entrypoints. And it feels like doing so would simplify things.
serverful server.ts <-- 1 serverful entrypoint server/ <-- 1 serverful entrypoint (dir style) index.ts servers/ <-- OR N serverful entrypoints a.ts b.ts servers/ <-- OR N serverful entrypoints (dir style) a/ index.ts b/ index.tsserverless lambda.ts <-- 1 serverless entrypoint lambda/ <-- 1 serverless entrypoint (dir style) index.ts lambdas/ <-- OR N serverful entrypoints a.ts b.ts lambdas/ <-- OR N serverful entrypoints (dir style) a/ index.ts b/ index.ts -
The simplicity comes from the fact that there are fewer rules to learn. There is a dimension of server kind and there is a dimension of number. No exceptions.
-
Naming is hard
-
serverless things: lambda / serverless / func / function
-
serverful things: server / process
-
ambiguous things: service
-
other things: app / project
-
I somehow like func/funcs more than lambda/lambdas...
- easier to spell
- easier to write
- easier to say (feels like one less syllabol)
func.ts <-- 1 serverless entrypoint func/ <-- 1 serverless entrypoint (dir style) index.ts funcs/ <-- OR N serverful entrypoints a.ts b.ts funcs/ <-- OR N serverful entrypoints (dir style) a/ index.ts b/ index.ts
-
I am currently using nexus-schema with nexus-schema-plugin-prisma with next.js and it works very well. I tried to "upgrade" to nexus and nexus-plugin-prisma with the 0.21.1 update but ran into few issues
use(prisma())gave me theError: You most likely forgot to initialize the Prisma Client.error. I bypassed that by doing
use(
prisma({
client: { instance: new PrismaClient() },
})
)
- That gave me the following error
TypeError: Converting circular structure to JSON
| --> starting at object with constructor 'Object'
| | property 'fields' -> object with constructor 'Array'
| | index 4 -> object with constructor 'Object'
| | ...
| | property 'outputType' -> object with constructor 'Object'
| --- property 'type' closes the circle
from this location: nexus/dist/lib/reflection/reflect.js:36:46
Please let me know if I can do anything to help solve this. You folks are doing a great job and I look forward to see further work on next.js integration.
That gave me the following error
Hey @kristoferma, thanks for the kind words. Ah, I think I know why yes. We'll fix that soon, maybe tomorrow. If you file a bug issue for it that would be great too.
@Weakky We'll need to not assume that all plugin settings are JSON serializable. I kind of saw this coming but now here we are.
Some additional thoughts came up with @Weakky today while sprint planning.
-
Going between serverless and serverful should not be mutually exclusive. This is a significant change in our thinking.
-
How do we talk about this? Is there one app with zero or more lambdas and zero or one server? Or is there one project with zero or more serverless apps and zero or one serverful app? Does a project have an app or does app/project mean the same thing? Is it that a project has multiple apps or that an app has multiple entrypoints? "Entrypoints" instead of "apps"? ...
- ...Thinking about this more considering that Nexus calls its default import
appit seems that we should consider every entrypoint its own app and the larger context be a project.
- ...Thinking about this more considering that Nexus calls its default import
-
In dev mode, a serverless platform emulator would run the lambda entrypoints, and the serverful entrypoint. But reflection on each could lead to different typegen. Typegen is global. There would be conflicts.
@Weakky maybe we can look into tsconfig paths to find way to have each lambda consume only its respective typegen...
-
Build would needs to run per entrypoint. And if we cannot find solution to global typegen (see above point), then serially.
-
Nexus will need to create a bundle per lambda. This does overlap with what e.g. Next.js / Vercel provide, but we think that's ok. Just like we wanted a bundle/tree-shake step #119 but again Next.JS also provides that. The point is integrations will always have some feature overlap with pure Nexus, and Nexus should just be as much as possible flexible enough to turn off the overlapping parts.
-
If there is a convention for serverless then we probably need a convention for serverful, e.g.
server.tsbecomes the serverful entrypoint for the app. For example in the following there would be three entrypoints in the app.``` server.ts <-- serverful entrypoint lambdas/ graphql.ts <-- serverless entrypoint 1 playground.ts <-- serverless entrypoint 2 ``` -
Re bundles, given the above example, the build result would be e.g.:
``` node_modules/ .build/ server.js <-- (self-contained bundle) lambdas/ graphql.js <-- (self-contained bundle) playground.js <-- (self-contained bundle) ``` -
Plugins could tailor further to platform needs, e.g.
nexus-plugin-aws-lambdacould zip the lambdas etc. -
~Lambdas would use file paths to infer URL paths. Config overrides etc. would probably be available somehow.~ See points way below.
-
All the different entrypoints, if they constitute their own apps, will need their own app state. Changing the schema or changing the settings should apply to a specific entrypoint, not all. ~Of course the classic way to do this is build up state at the module level from stateless imports, then export/import everywhere to build up a graph of shared module deps where needed, etc. But that's really against the Nexus way (today). How does this problem look to Nexus.~ To achieve this dev mode will need to run each entrypoint in its own sub process. A very large app of dozens of entrypoints could lead to serious performance problems. But we're not sure about that. And maybe most apps will only have a few entrypoints. Anyways being limited here could be an acceptable way to start out.
If we don't run each entrypoint in its own process we'll get the kind of problems we're seeing in nextjs dev.
-
Multiple serverless entrypoints in an app look an aweful lot like multiple serverful apps in a monorepo. ...? It feels like we can generalize the system further to accept zero-or-more serverless AND serverful entrypoints. And it feels like doing so would simplify things.
serverful server.ts <-- 1 serverful app server/ <-- 1 serverful app (dir style) index.ts servers/ <-- N serverful app a.ts b.ts servers/ <-- N serverful app (dir style) a/ index.ts b/ index.tsserverless lambda.ts <-- 1 serverless app lambda/ <-- 1 serverless app (dir style) index.ts lambdas/ <-- N serverless apps a.ts b.ts lambdas/ <-- N serverless apps (dir style) a/ index.ts b/ index.ts -
The simplicity comes from the fact that there are fewer rules to learn. There is a dimension of server kind and there is a dimension of number. No exceptions.
-
Naming is hard
-
serverless things: lambda / serverless / func / function
-
serverful things: server / process
-
ambiguous things: service
-
other things: app / project
-
I somehow like func/funcs more than lambda/lambdas...
- easier to spell
- easier to write
- easier to say (feels like one less syllabol)
func.ts <-- 1 serverless app func/ <-- 1 serverless app (dir style) index.ts funcs/ <-- N serverful apps a.ts b.ts funcs/ <-- N serverful apps (dir style) a/ index.ts b/ index.ts
-
-
We'll need to revise the schema module auto-import system given multiple entrypoints, since, how can we know which entrypoint a schema module is associated with?
To solve this we can use the disk as a scoping mechanism like as follows:
server/ index.ts <-- app A graphql.ts <-- only visible to app A funcs/ b/ index.ts <-- app B graphql.ts <-- only visible to app B c/ index.ts <-- app C graphql.ts <-- only visible to app C common/ graphql.ts <-- visible to apps A B CI'm also realizing that this makes possible for a user to do something like this inside the common modules:
import { server } from 'nexus' server.middleware(...) // all lambdas will get this!However if you want e.g. all modules - 1 exception then you're out of luck... unless we introduced a new apps selection api... e.g.:
import apps from 'nexus/apps' apps('b', 'c').server.middleware(...) // app A will not get this! apps('a','b').schema.objectType(...) // app C will not get this!But this feels odd to me right now... probably we can drop this train of thought for now.
-
Thinking more about the conventional naming strategies. We haven't explored prefixes/suffixes.
server.ts <-- 1 serverful app server/ <-- 1 serverful app (dir style) index.ts a.server.ts <-- OR N serverful apps b.server.ts a.server/ <-- OR N serverful apps (dir style) index.ts b.server/ index.tsfunc.ts <-- 1 serverless app func/ <-- 1 serverless app (dir style) index.ts a.func.ts <-- N serverful apps b.func.ts a.func/ <-- N serverful apps (dir style) index.ts b.func/ index.tsIn order to support better grouping in file trees etc. we might want prefix instead:
server.ts <-- 1 serverful app server/ <-- 1 serverful app (dir style) index.ts server.a.ts <-- OR N serverful apps server.b.ts server.a/ <-- OR N serverful apps (dir style) index.ts server.b/ index.tsfunc.ts <-- 1 serverless app func/ <-- 1 serverless app (dir style) index.ts func.a.ts <-- N serverful apps func.b.ts func.a/ <-- N serverful apps (dir style) index.ts func.b/ index.tsRemoving a [forced] level of nesting in project layout is great. However, for serverless funcs, has the issue of complicating the file-systems-as-url.
Before
funcs/ foo/ qux/ bar.ts <-- POST /foo/qux/aAfter??
func.a/ foo/ qux/ index.ts <-- POST /foo/qux/a ????Ultimately I'm not sure the file-system-as-url-path is a great idea, though. It is very rigid. For example maybe a handler wants to be available at multiple paths or methods... Maybe this is where Nexus draws the line and we stick to APIs, e.g.:
// func.a.ts import { func } from "nexus"; func.routes.add({ method: "POST", path: "/a/b/c", }); func.routes.add({ method: "GET", path: "/z/y/x", });or e.g.
// func.a.ts import { settings } from 'nexus' settings.change({ func: { routes: { method: 'POST', path: '/a/b/c', }, { method: 'GET', path: '/z/y/x' } } })or e.g. maybe Nexus has a centralized routing table system:
// imagine this module is in some common/top-level place in the project // e.g. routing.ts import { routing } from 'nexus/funcs' routing.a.add({ methods: ['POST', 'GET'], path: '/a/b/c' }) routing.b.add({ method: 'POST', path: '/a/b/z' }) routing.c.add({ method: 'DELETE', path: '/a/b/foo' }) // ^----- app names, typegenThen Nexus would use reflection to extract data and generate the config actually needed in the e.g.
serverless.ymletc. file.In fact, even for a NextJS integration, maybe Nexus could generate modules into
/api!?Unlike working with the file-system an API approach is type safe, works with IDE workflows, including refactoring and so on. It seems to scale much better.
@Weakky IIUC this was more or less your position on the call today right? I think I'm onboard now.
Next.js encourages users to fetch initial props for apps via getServerSideProps method of each page component. They encourage users not to use api routes but to "write server side code directly" docs. This would require a new way of interfacing with nexus without launching a server.
The easiest way to do this is to expose a method to execute graphql queries without starting a server. Something like this:
import { execute } from 'nexus'
export const getServerSideProps = async ({ params }) => {
const response = execute(params.query, params.variables)
return JSON.stringify(response)
}
Hey @kristoferma, yeah we're aware, from the docs:
That means you can write code such as direct database queries without them being sent to browsers. You should not fetch an API route from getStaticProps — instead, you can write the server-side code directly in getStaticProps.
This means that one should be using, for example, and generally, Prisma Client directly, not an API at all.
Of course, that's the general idea. A user in should do what they need to in their scenario.
interfacing with nexus without launching a server.
Using Nexus without a server is already possible https://www.nexusjs.org/#/guides/serverless
Hey @kristoferma sorry missed your post before, about it:
use(prisma()) gave me the Error: You most likely forgot to initialize the Prisma Client. error. I bypassed that by doing
This was a bug, its fixed now in latest canary.
That gave me the following error
Plugin settings reflection was enhanced to support non-serializable settings data. So again, latest canary this is fixed.
Plugin settings reflection was enhanced to support non-serializable settings data. So again, latest canary this is fixed.
Thanks, this fixed the issue for me
This means that one should be using, for example, and generally, Prisma Client directly, not an API at all.
I should have explained my use case better. I am using Relay with Next.js and I want to server side render my page and pass on the Relay Store caching from the server to the client. To do this I must fetch the data with relay on the server, stringify it as json and pass it on as props via GetServerSideProps. If I use Prisma Client directly, I lose the ability to pass the Relay store from the server to the client.
Currently I fetch the data from the graphql endpoint and it works fine, but it adds an extra step that could be bypassed by executing the graphql query from the GetServerSideProps
@kristoferma Ah my mistake, you were talking about getServerSideProps and I replied about getStaticProps.
Currently I fetch the data from the graphql endpoint and it works fine, but it adds an extra step that could be bypassed by executing the graphql query from the GetServerSideProps
It seems like what you need is the layer right below the handler. You might be able to hack a solution right now with Nexus' current serverless support.
Your use-case is interesting. If you can create a new issue for that'd be great.
Hello @jasonkuhrt, I am enjoying getting to grips with nexus and would like to deploy with lambda. If this is already possible then it would be useful to reference an example lambda handler function. Perhaps I am jumping ahead slightly though. Thanks
Perhaps I am jumping ahead slightly though
Hey, yeah slightly. You're kind of on your own right now. I actually think it might be possible now with the primitives we've shipped but, not sure. Check out https://www.nexusjs.org/#/guides/serverless.
Now that we have an integrated bundle step, it means you should be able to zip the build and ship to AWS Lambda. But again we haven't explicitly made this a feature/goal yet, its on the roadmap.
I'm trying to use with Serverless AWS but I've problem with the export handlers because the graphQL handler cant load all the schema files.
▲ nexus:schema Your GraphQL schema is empty. This is normal if you have not defined any GraphQL types yet. If you did however, check that your files are contained in the same directory specified in the `rootDir` property of your tsconfig.json file.
If we access the playground using the yarn start it works.
In serverless it works too, but It cant find any of the schema files. The playground works but empty.
Manually changing the deployed files and moving the require files from the index.js to app.js solves the issue.
There is a copy of the app.ts
// app.ts
import app, { use, server } from 'nexus'
import { prisma } from 'nexus-plugin-prisma'
import serverless from 'serverless-http'
use(prisma())
app.assemble()
export const graphql = serverless(server.handlers.graphql, {
request(request: any, event: any, context: any) {
const { body } = request as any
request.context = event.requestContext;
request.body = JSON.parse(body.toString()) // parsing body bc body is some weird buffer thing
return request;
}
})
export const playground = serverless(server.handlers.playground)
Is it somehow possible to bundle the schema files into the built app.js?
This happens for me because there are two instances of Prisma, one in nexus-plugin-prisma node_modules and another one in the root project node_modules.
I fix this by deleting node_modules/nexus-plugin-prisma/node_modules/.prisma after npm install
This is probably a bug but I have not submitted an issue on this
This happens for me because there are two instances of Prisma, one in nexus-plugin-prisma node_modules and another one in the root project node_modules.
I fix this by deleting
node_modules/nexus-plugin-prisma/node_modules/.prismaafter npm installThis is probably a bug but I have not submitted an issue on this
This not solve my issue, sadly.
In my environment I don't have this duplicated Prisma.
Pretty sure is because we are missing the schema required files on the handler call.
In the non-index.js entrypoint we have to use in order to export the handlers.
@kristoferma
and another one in the root project node_modules.
Nexus Prisma apps depending on prisma deps directly is not supported.
@zapbr
Is it somehow possible to bundle the schema files into the built app.js?
That's what nexus build effectively does, but it preserves the file tree.
@jasonkuhrt the necessary schema files are bundled into index.js, which does not contain any of the exported handlers from app.ts. So in order to use nexus with the serverless framework, currently my understanding is that we need to copy all the require statements into the built app.js (manual retouching of built files).
@kldzj will have to look into it. Like I mentioned to toddpla you're a bit ahead of us. If you want to contribute a serverless example to examples repo that might be a good place to explore the issue more precisely.
Hallo @jasonkuhrt, thank you for your quick response. So we build a little demo with the issue:
https://github.com/zapbr/nexus-prisma-aws-serverless-demo
This is a hack, but if you just import your schema files, you'll force it into the bundle.
import app, { server, use } from "nexus";
import { prisma } from "nexus-plugin-prisma";
import * as serverless from "serverless-http";
import "./graphql/User"; // hack to resolve https://github.com/graphql-nexus/nexus/issues/782
use(prisma({ features: { crud: true } }));
app.assemble();
Hoping for a better solution soon!
@AaronBuxbaum great find, but seems very ugly when you have a lot of models. We decided to just migrate back to @nexus/schema.
@AaronBuxbaum that's with nextjs correct?
@kldzj I'm assuming there was another reason than that since you'll have to import modules just the same with @nexus/schema. What was the reason in your case?
@AaronBuxbaum that's with nextjs correct?
Currently, no (I'm playing with serverless -> AWS Lambda), but I suspect it would be easy to put this on NextJS if desired
@jasonkuhrt wanting to use federation was another reason, but mainly for ease of serverless deployment.
@AaronBuxbaum If you can contribute an example or recipe that would be great. It seems that your technique is similar to our nextjs recipe.
I made it work with Webpack + Serverless + Lambda but I am not sure if the solution is feasible. Basically, like the #109 issue says, you get a lot of warnings if you use sls and webpack, because of the usage of require.resolve(). However, if you wrap it on eval() those are executed at runtime and they seem to work just fine (it took me like 50 webpack google searches to get there). So, I manually modified the files with warnings and I got it to work locally with sls offline and I also was able to deploy to Lambda, however you get the error from the require right now, because the code does not include the fix. I'll try to bundle manually and see if I can confirm this, however, here are some snippets in case it helps:
Note: I could not make the typescript serverless plugin to work so I run tsc, prisma generate and nexus build before deploy.
//app.ts
import app, { use, settings, server} from 'nexus'
import { prisma } from 'nexus-plugin-prisma'
import serverless = require('serverless-http')
import './graphql/graphql'; //Here is my Nexus schema definitions
settings.change({
logger: {
pretty: true
},
server: {
startMessage: (info) => {
settings.original.server.startMessage(info)
},
},
schema: {
generateGraphQLSDLFile: './graphql/schema.graphql'
}
})
use(
prisma(
{
migrations: false,
features: {
crud: true
}
}
)
)
app.assemble()
export const graphql = serverless(server.handlers.graphql, {
request(request: any, event: any, context: any) {
const { body } = request as any
request.context = event.requestContext;
request.body = JSON.parse(body.toString()) // parsing body bc body is some weird buffer thing
return request;
}
})
export const playground = serverless(server.handlers.playground)
//serverless.yml
service: graphql-lambda
plugins:
# - serverless-plugin-typescript
- serverless-webpack
- serverless-offline
custom:
prune:
automatic: true
number: 5
serverless-offline:
port: 1337
webpack:
webpackConfig: 'webpack.config.js' # Name of webpack configuration file
includeModules: true # Node modules configuration for packaging #I dont think this is needed anymore
packager: 'yarn' # Packager that will be used to package your external modules
provider:
name: aws
runtime: nodejs12.x
stage: dev # Set the default stage used. Default is dev
region: us-east-1 # Overwrite the default region used. Default is us-east-1
profile: production # The default profile to use with this service
memorySize: 512 # Overwrite the default memory size. Default is 1024
deploymentBucket:
name: com.serverless.${self:provider.region}.deploys # Overwrite the default deployment bucket
serverSideEncryption: AES256 # when using server-side encryption
tags: # Tags that will be added to each of the deployment resources
environment: ${self:provider.stage}
service: serverless
deploymentPrefix: serverless # Overwrite the default S3 prefix under which deployed artifacts should be stored. Default is serverless
versionFunctions: true #false # Optional function versioning
functions:
playground:
handler: app.playground
events:
- http:
path: playground
method: get
cors: true
graphql:
handler: app.graphql
events:
- http:
path: graphql
method: get
cors: true
- http:
path: graphql
method: post
cors: true
//webpack.config.js
const slsw = require('serverless-webpack');
const CopyPlugin = require('copy-webpack-plugin')
const path = require('path');
module.exports = {
entry: slsw.lib.entries,
target: 'node',
mode: slsw.lib.webpack.isLocal ? "development": "production",
output: {
libraryTarget: 'commonjs',
path: path.join(__dirname, '.webpack'),
filename: '[name].js',
pathinfo: false,
},
optimization: {
minimize: false
},
plugins: [
new CopyPlugin({
patterns: [
{ from: './prisma/schema.prisma' },
{ from: './.nexus', to: './.nexus'},
{ from: './node_modules/.prisma', to: './node_modules/.prisma'},
{ from: './node_modules/nexus-plugin-prisma', to: './node_modules/nexus-plugin-prisma'},
]
})
]
};
Example that I had to change in typegenAutoConfig.js
resolvedPath = eval(`require.resolve("${pathOrModule}", {
paths: [${process.cwd()}],
})`);
I also had to make similar changes on files like manifest.js, linkable.js, couple of utils.js, import.js, etc.
After this, it will complain that it cannot find the core, so I also had to change the import order on @nexus/schema/dist-esm/index.js, so this line is at the end, after the imports:
export { core, blocks, ext };
After this, sls offline will work just fine and I can query the database normally. Also, my deployed function on Lambda is about 21mb which is completely fine.
As I said, this does not work just yet on deployment. It will complain about the plugin not being able to find because I have to assume sls is just doing yarn and getting the npm module which does not have the fix. I'll try to work on that next.
Dropping by to say I have a blast combining NextJS, Prisma and Nexus with the experimental serverless API. My /api/graphql endpoint looks something like this:
// pages/api/graphql.ts
import app, { settings } from "nexus";
import nextConnect from "next-connect";
import { sessionMiddleware, randomMiddleware } from "../../lib/middleware";
require("../../graphql/schema");
settings.change({
server: {
path: "/api/graphql", // for playground
},
});
app.assemble();
export default nextConnect()
.use(sessionMiddleware)
.use(randomMiddleware)
.use(app.server.handlers.graphql);
Thank you for your hard and much appreciated work on Nexus
I tried all of code above but I can not run my serverless. Does Nexus support serverless?
@heibel Could you please share the rest of your setup? I am still trying to deploy to lambda and while it works locally, it is because of the manual changes that I did to the code and I could not make the setup to work effortlessly. Thanks
@sfratini I forgot to mention I deploy to/with Vercel. Which works with the above code. Maybe you can share your code/lambda errors?
@sfratini I forgot to mention I deploy to/with Vercel. Which works with the above code. Maybe you can share your code/lambda errors?
Sure, so basically it is not finding the prisma plugin. Now, the plugins are loaded using require.resolve which webpack does not like. I "solved" that locally by replacing all the places nexus uses that, with eval() calls which are executed at runtime (you can see my changes a couple of comments above). The issue however, is that serverless does an install which, obviously installs the version without the change so on the cloud, the plugins won't work. (with webpack)
I need to use webpack (or any other packager/compression tool), because lambda has a 250MB limit.
| 2020-08-17T09:35:48.525+02:00 | [90m 57 [39m[31m✕ [39m[31mnexus[39m There were errors loading 1 or more of your plugins. | |
|---|---|---|