Typetta is the only open-source library that allows you to expose your database via a fully auto-generated GraphQL API.
Unlike other services that have the same goal, being just a small and light package you can use Typetta on your preffered NodeJS stack and with your own infrastructure.
There are several ways a GraphQL API of your data can be useful for your organization:
As a POC back-end, for systems that only needs CRUD API over a simple data model.
As a private API to access your data machine-to-machine, with a standard query language that is completely agnostic about the underlying database technology.
As a flexible and extensible starting point to build your custom GraphQL back-end.
Having such an auto-generated with just a few lines of code allows you to focus on your business.
Typetta provide ready-to-use typedefs and resolvers that you can easily use to create a GraphQL Endpoint with all the CRUD operations to manage the entities of your data model. The Typetta solution is fully compatible with all GraphQL servers on NodeJS (GraphQL Yoga, Apollo Server, Mercurius, express-graphql).
Following an example using Apollo Server and generated code from the default src/generated
directory:
import { ApolloServer } from 'apollo-server'
import { resolvers } from './generated/resolvers'
import { mergeTypeDefs } from '@graphql-tools/merge'
import inputTypeDefs from './generated/operations'
import schemaTypeDefs from './user.typedefs'
import { typeDefs as typettaDirectivesTypeDefs } from '@twinlogix/typetta'
import { EntityManager } from './generated/typetta'
export type Context = { entityManager: EntityManager }
const server = new ApolloServer({
typeDefs: mergeTypeDefs([
inputTypeDefs,
schemaTypeDefs,
typettaDirectivesTypeDefs,
]),
resolvers,
context: () => ({ entityManager: new EntityManager() }),
})
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`)
})
Typedefs and resolvers are pretty standard components of a GraphQL Schema. You can pass them directly to your server and start a GraphQL Endpoint with default CRUD operations, but you can also compose them in a more custom implementation, using partially generated code and overring what you need.
This gives you maximum flexibility in the implementation of your backend. Adding some kind of authentication as a middleware, you will have a ready to use GraphQL back-end with nearly zero custom implementations.
Take this model as example:
type User @entity @memory {
id: ID! @id(from: "generator")
name: String!
posts: [Post!] @foreignRef(refFrom: "userId")
}
type Post @entity @memory {
id: ID! @id(from: "generator")
userId: ID!
content: String!
metadata: Metadata
}
type Metadata {
tags: [String!]
views: Int
}
and this Typetta configuration:
schema: src/schema.ts
outputDir: src/generated
generateGraphQLOperations:
context:
type: '../index#Context'
path: entityManager
After the Typetta generation, inside the folder src/generated
will be present the files needed to setup a working GraphQL endpoint. The endpoint will offer CRUD (insert, update, and read) operations for all the @entity
defined in the model. By configuring the security layer you can also setup a secured endpoint.
Here some example of Mutation and Query that can be performed:
query findMyUser {
users(filter: { id: { eq: "123" } }) {
name
posts {
content
}
}
}
mutation createNewPost {
createPost(
record: {
userId: "123"
content: "D&D is cool"
metadata: { views: 1, tags: ["fun", "games"] }
}
) {
id
}
}
mutation deleteSomePosts {
deletePosts(filter: { id: { in: ["1", "2"] } })
}
mutation updatePost {
updatePosts(filter: { id: { eq: "1" } }, changes: { metadata: { views: 2 } } )
}
The input type of filters, updates, inserts and sorts are very similar to the Typetta API. Some differences are in the filter input type, specifically the filtering by a field inside an embedded are not implemented with the dot notation but with embedded notation. Same thing for the update input type.
$and
, $or
and $nor
operator are renamed to and_
, or_
and nor_
because GraphQL does not support $
character.
The has
filter operator is present for all fields that are arrays and only match if the array contains one or more elements that match the value. This filter: filter: { metadata: { tags: { has: "fun" } } }
match all posts that have the fun tag inside the metadata.
A complete example can be found here.