This project is designed to expose Dynamo to the GraphQL as an ORM as detailed on GraphQL Schema Directives as ORM by Tobias Meixner.
The idea is eventually be able to drive the entire backend through the schema without a need for handwriting any resolvers or mutations.
For an example of this in action, see the Event App.
At Aloompa, we use DynamoDB for a lot of stuff. Due to the complexity of our authentication layer, we aren't able to migrate to AWS AppSync because they don't allow custom directives, but we also have felt the pain of writing an excess of boilerplate along with the Vogels and Dynogles libraries on top of DynamoDB going unmaintained. We started this library as an experiment to see how far we could get using GraphQL as our ORM on top of raw queries using the AWS SDK.
There will be some opinions built into this directive, such as the shape of lists and mutation responses. I will endeavor to follow the best practices laid out by Facebook, Apollo and the AWS Amplify team, but if you don't like falling into the pit of success, this library might not be your thing.
This is obviously a very new project and is not suitable for production applications yet, so use it with caution and please contribute to the project if you think it could be useful.
With NPM:
npm i @aloompa/dynamo-graphql-directive -S
With Yarn:
yarn add @aloompa/dynamo-graphql-directive --save
import { ApolloServer } from 'apollo-server';
import { createDynamoSchema } from '@aloompa/dynamo-graphql-directive';
import { typeDefs } from './schema';
const server = new ApolloServer({
typeDefs,
resolvers: {},
schemaDirectives: {
dynamo: createDynamoSchema({
// Your custom AWS Dynamo config goes here
endpoint: 'http://localhost:8000'
})
}
});
server.listen().then(({ url }) => {
console.log(`π Server ready at ${url}`);
});
For a full list of what can be passed in to the createDynamoSchema
function, check out the AWS docs
Additionally, there are options, you may pass in as a second argument to createDynamoSchema
:
If you prefix or postfix all of your table names with the development environment, this can be handy to override the table names. Everytime we get the name of a table, we pass it into this function so that you can return a new formatted name.
createDynamoSchema({ ... }, { getTableName: name => `${name}-dev` })
To run the examples, you need to install dynamo local. The instructions for that can be found in the AWS docs.
After it is set up, you just need to run java -Djava.library.path=./DynamoDBLocal_lib -jar DynamoDBLocal.jar -sharedDb
to run the database and then navigate to the ./packages/dynamo-graphql-example
directory and run:
yarn migrations; yarn start
type Event {
id: String
name: String
}
type EventsCollection {
items: [Event]
}
type Query {
listEvents: EventsCollection @dynamo(table: "events")
}
type Event {
id: String
name: String
}
type Query {
getEvent(id: String!): Event @dynamo(table: "events", action: "get")
}
type Event {
id: String
name: String
}
type EventsCollection {
items: [Event]
}
type Query {
searchEventsByName(query: String!): EventsCollection
@dynamo(
table: "events"
action: "query"
index: "name-index"
primaryKey: "name"
comparator: "EQ"
)
}
Queries may also include a valid comparator
to declare how the items are being filtered. These include:
- BETWEEN
- BEGINS_WITH
- EQ
- LT
- LTE
- GT
- GTE
If the comparator is BEGINS_WITH
, EQ
, LT
, GT
, LTE
, GT
or GTE
, a query
is expected to be provided as an argument.
If the comparator is BETWEEN
, a min
and max
argument is expected.
type Place {
id: String
name: String
}
type Event {
id: String
name: String
placeId: String
place: Place @dynamo(table: "places", action: "get", foreignKey: "placeId")
}
type Query {
getEvent(id: String!): Event @dynamo(table: "events", action: "get")
}
Coming soon
type Event {
id: String
placeId: String
}
type Place {
id: String
listEvents: EventsCollection
@dynamo(
table: "events"
index: "placeId-index"
primaryKey: "placeId"
action: "query"
)
}
type Query {
listEvents: EventsCollection @dynamo(table: "events")
listPlaces: PlacesCollection @dynamo(table: "places")
}
type EventsCollection {
items: [Event]
}
type PerformersCollection {
items: [Performer]
}
type Performer {
id: String
listEvents: EventsCollection
@dynamo(
table: "events"
joinTable: "events-performers"
index: "performerId-index"
primaryKey: "performerId"
foreignKey: "eventId"
action: "query"
)
}
type Event {
id: String
listPerformers: PerformersCollection
@dynamo(
table: "performers"
joinTable: "events-performers"
index: "eventId-index"
primaryKey: "eventId"
foreignKey: "performerId"
action: "query"
)
}
type Query {
listEvents: EventsCollection @dynamo(table: "events")
listPerformers: PerformersCollection @dynamo(table: "performers")
}
type Event {
id: String
name: String
}
type EventMutationResponse {
code: String
message: String
item: Event
}
input EventInput {
name: String
}
type Mutation {
createEvent(input: EventInput): EventMutationResponse
@dynamo(table: "events", action: "create")
}
type Event {
id: String
name: String
}
type EventMutationResponse {
code: String
message: String
item: Event
}
input EventInput {
name: String
}
type Mutation {
updateEvent(id: String!, input: EventInput): EventMutationResponse
@dynamo(table: "events", action: "update")
}
type Event {
id: String
name: String
}
type EventMutationResponse {
code: String
message: String
item: Event
}
type Mutation {
destroyEvent(id: String!): EventMutationResponse
@dynamo(table: "events", action: "destroy")
}
We encourage you to contribute to Dynamo GraphQL Directive by submitting bug reports and pull requests through Github.
Dynamo GraphQL Directive is released under The MIT License (MIT)
Copyright (c) [2019][aloompa llc]
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.