graphql-santa v0.0.0-master.1983f41
Please beware that this is a PROTOTYPE. Do NOT use this for serious work. Thanks! 🎄graphql-santa
graphql-santa is a GraphQL API framework. It takes a code-first approach and brings together a set of tools that provide robust type safety so that if your app compiles, you have a high degree of confidence that it works.
Tools and libraries used include:
- TypeScript
- Express
- Nexus
- Apollo Server
Contents
Tutorial
For this tutorial we will use Prisma with PostgreSQL. Install PostgreSQL if needed and then get its connection URL. Check out our postgresql setup guide if unsure.
Kick off a new project. Say yes (
y) to the prisma option. ChoosePostgreSQLfor the db option.$ npx graphql-santaOur Hello World schema doesn't account for information about moons, lets change that.
Start by updating our data layer to model information about moons. We don't want to go crazy scientific here but a bit of modelling will serve us well. A world may have more than one moon, and a moon may have properites in its own right. So lets give moons a first class model representation. Then, we can connect them to their respective worlds:
+++ prisma/schema.prisma model World { id Int @id name String @unique population Float + moons Moon[] } + model Moon { + id Int @id + name String + world World + }graphql-santareacts to changes in your Prisma schema. By saving the above, your dev database will be automatically migrated and photon regenerated. You literally now just move on to updating your GraphQL API.We have data about
Earthfrom before, but now we need to update it with information about its moon. Instead of working with photon inside one-off scripts, lets enhance our API and make the update as if a client app were.We're going to need to expose the
moonsworld field to clients+++ src/schema.ts app.objectType({ name: "World", definition(t) { t.model.id() t.model.name() t.model.population() + t.model.moons() } })Upon doing this however, we will see a warning in our dev mode logs:
Warning: Your GraphQL `World` object definition is projecting a field `moons` with `Moon` as output type, but `Moon` is not defined in your GraphQL Schema Warning: in /Users/jasonkuhrt/foobar/src/schema.ts:10:13 6 definition(t) { 7 t.model.id(); 8 t.model.name(); 9 t.model.population(); → 10 t.model.moons();The feedback is pretty clear already but to restate: The problem is that we're project a Prisma model field (
moons) that is a connection to another Prisma model (Moon) that has not been projected on our API layer. So let's do that now:+++ src/schema.ts +app.objectType({ + name:'Moon', + definition(t){ + t.model.id() + t.model.name() + t.model.world() + } +})Do not copy-paste. Instead type this out yourself and take note how autcompletion within the
definitionblock ont.modeleffectively guides you to success.Once you have projected
Moonfrom your data layer to your API layer, you will see that the dev mode warning and TypeScript error are now resolved. 🙌If you go to your GraphQL Playground now you will see that your GraphQL schema now contains your Moon data shape too. But of course we still need to update
Earthwith data about its moon. To achieve that we're going to expose CRUD actions that clients can use to updateEarth.+++ src/schema.ts +app.mutationType({ + definition(t){ + t.crud.updateOneWorld() + } +})Again do not copy-paste. Type this out and see how it feels. Notice how auto-completion guides you from start to finish.
If we go back to our schema in GraphQL Playground now, we'll see a significant number of additions to the schema, a result of the CRUD features we've just enabled.
Now, let's give
Earthits moon!mutation addMoonToEarth { updateOneWorld( where: { name: "Earth" } data: { moons: { create: { name: "moon" } } } ) { name moons { name } } }You should see a result like:
{ "data": { "updateOneWorld": { "name": "Earth", "moons": [ { "name": "moon" } ] } } }Deploy to Heroku
For this step, create an account at Heroku and setup the CLI.
- Create a new app:
heroku apps:create - Attach your project to the app:
heroku git:remote --app <app-name> - Add a postgres database to it:
heroku addons:create heroku-postgresql --app <app-name> - Get the postgres database credentials:
heroku pg:credentials:url --app <app-name> - Export the connection URL into your shell
export DATABASE_URL="<connection-url>" - Initialize the postgres database:
npx graphql-santa db init - Deploy using the git push to master workflow. See your app running in the cloud!
- Create a new app:
Conclusion
Hopefully that gives you a taste of the power under your finger tips. There's a ton more to discover. Happy coding! 🙌
Recipes
Add Prisma
Prisma Framework is a next-generation developer-centric tool chain focused on making the data layer easy. In turn, graphql-santa makes it easy to integrate Prisma Framework into your app.
Install the prisma plugin
$ npm install graphql-santa-plugin-prismaAdd a
schema.prismafile. Add a datasource. Here we're working with SQLite. Add photon.+++ prisma/schema.prisma + + datasource db { + provider = "sqlite" + url = "file:dev.db" + } + + generator photonjs { + provider = "photonjs" + }Initialize your database
$ npx santa db initDone. Now your app has:
- Functioning
$ santa db nexus-prismaNexus plugin allowing e.g.:+++ src/schema.ts objectType({ name: 'User', definition(t) { - t.id('id) - t.string('name') + t.model.id() + t.model.name() }, })An instance of the generated Photon.JS client is a added to context under
photonproperty, allowing e.g.:+++ src/schema.ts queryType({ definition(t) { t.list.field('users', { type: 'User', - resolve() { - return [{ id: '1643', name: 'newton' }] + resolve(_root, _args, ctx) { + return ctx.photon.users.findMany() }, }) }, })The TypeScript types representing your Prisma models are registered as a Nexus data source. In short this enables proper typing of
parentparameters in your resolves. They reflect the data of the correspondingly named Prisma model.
- Functioning
Setup a local PostgreSQL
The reccommended way to run postgres locally is with docker, because it is easy flexible and reliable.
Start a postgres server for your app:
docker run --detach --publish 5432:5432 --name 'postgres' postgresNow you can use a connection URL like:
postgresql://postgres:postgres@localhost:5432/myapp
If you don't want to use a docker, here are some links to alternative approaches:
Go to proudction
Add a build script
+++ package.json + "build": "santa build"Add a start script
+++ package.json + "start": "node node_modules/.build"In many cases this will be enough. Many deployment platforms will call into these scripts by default. You can customize where
buildoutputs to if your deployment platform requires it. There are built in guides forzeitandherokuwhich will check your project is prepared for deployment to those respective platforms. Take advantage of them if applicable:+++ package.json + "build": "santa build --deployment now"+++ package.json + "build": "santa build --deployment heroku"
Go to production with Prisma, Heroku, Heroku PostgreSQL
- Confirm the name of the environment variable that Heroku will inject into your app at runtime for the database connection URL. In a simple setup, with a single attached atabase, it is
DATABASE_URL. Update your Prisma Schema file to get the database connection URL from an environment variable of the same name as in step 1. Example:
--- prisma/schema.prisma +++ prisma/schema.prisma datasource postgresql { provider = "postgresql" - url = "postgresql://<user>:<pass>@localhost:5432/<db-name>" + url = env("DATABASE_URL") }Update your local development environment to pass the local development database connection URL via an environment variable of the same name as in step 1. Example with direnv:
Install
direnv$ brew install direnvHook
direnvinto your shell (instructions)Setup an
.envrcfile inside your project+++ .envrc + DATABASE_URL="postgresql://postgres:postgres@localhost:5432/myapp"Approve the
.envrcfile (one time, every time the envrc file changes).$ direnv allow .- Done. Now when you work within your project with a shell, all your commands will be run with access to the environment variables defined in your
.envrcfile. The magic ofdirenvis that these environment variables are automatically exported to and removed from your environment based on you being within your prject directory or not.
Conventions
schema.ts | schema/*
Optional –– Your GraphQL type definitions.
About
It can be a single module or folder of modules. Multiple instances of module/folder-modules throughout your source tree is supported.
In dev mode schema modules are synchronously found and imported at server boot time. At build time however static imports for all schema modules are inlined for boot performance.
Aliases
n/a
app.ts
Optional –– The entrypoint to your app
About
There can only be at most a single app.ts/server.ts/service.ts module in your source tree.
This module is optional when you just have schema modules and so graphql-santa already knows how import them into the final build. Otherwise you'll need this module to import your custom modules etc.
Aliases
server.ts service.tsExample Layouts
Nothing!
Nano
schema.tsMicro
app.ts
schema.tsBasic
src/
server.ts
schema.ts
prisma/
schema.prismaAPI
app
A singleton graphql-santa app. Use this to build up your GraphQL schema and configure your server.
Example
// schema.ts
import { app } from 'graphql-santa'
app.objectType({
name: 'Foo',
definition(t) {
t.id('id')
},
})app.addToContext
Add context to your graphql resolver functions. The objects returned by your context contributor callbacks will be shallow-merged into ctx. The ctx type will also accurately reflect the types you return from callbacks passed to addToContext.
Example
// app.ts
import { app } from 'graphql-santa'
app.addToContext(req => {
return {
foo: 'bar',
}
})
app.objectType({
name: 'Foo',
definition(t) {
t.string('foo', (_parent, _args, ctx) => ctx.foo)
},
})app.<nexusDefBlock>
Add types to your GraphQL Schema. The available nexus definition block functions include objectType inputObjectType enumType and so on. Refer to the official Nexus API documentation for more information about these functions.
Example
// schema.ts
import { app } from 'graphql-santa'
app.objectType({
name: 'Foo',
definition(t) {
t.id('id')
},
})app.server.start
Start the server. If you don't call this graphql-santa will. Usually you should not have to call it. Please share your use-case with us if you do!
CLI
$ santa --helpDevelopment
Overview
yarn
yarn test
yarn devTesting
Integration tests rely on npm link. This means those integration tests cannot
work on a machine that has not done npm link inside the root of the cloned
repo.
The reason we do not use yarn link is that yarn does not symlink the bin into
local node_modules.
Working With Example Apps via Linking
Refer to https://github.com/prisma-labs/graphql-santa-examples
Working with create command
In any example you can use this workflow:
rm -rf test-create && mcd test-create && ../node_modules/.bin/graphql-santa create6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago