Opinionated framework for building GraphQL APIs with strong conventions. With Warthog, set up your data models and the following are automatically generated:
- Database schema - generated by TypeORM
- Your entire GraphQL Schema including:
- types to match your entities - generated by TypeGraphQL
- GraphQL inputs for consistent creates, updates, filtering, and pagination inspired by Prisma's conventions
- A graphql-binding for type-safe programmatic access to your APIs.
- TypeScript classes for the generated GraphQL schema for type-safety while developing.
- Automatic validation before data is saved using any of the decorators available in the class-validator library.
The API for this library is still subject to change. It will likely shift until version 2, at which time it will become stable. I'd love early adopters, but please know that there might be some breaking changes for a few more weeks.
You must have Postgresql installed to use Warthog. If you're on OSX and have Homebrew installed, you can simply run:
brew install postgresql
Otherwise, you can install Postgres.app or use the Google machine to figure out how to install on your OS.
The easiest way to start using Warthog for a fresh project is to clone the warthog-starter repo. This has a simple example in place to get you started. There are also a bunch of examples in the examples folder for more advanced use cases.
Note that the examples in the examples folder use relative import paths to call into Warthog. In your projects, you won't need to set this config value as it's only set to deal with the fact that it's using the Warthog core files without consuming the package from NPM. In your projects, you can omit this as I do in warthog-starter.
yarn add warthog
The model will auto-generate your database table and graphql types. Warthog will find all models that match the following glob - '/**/*.model.ts'
. So for this file, you would name it user.model.ts
import { BaseModel, Model, StringField } from 'warthog';
@Model()
export class User extends BaseModel {
@StringField()
name?: string;
}
The resolver auto-generates queries and mutations in your GraphQL schema. Warthog will find all resolvers that match the following glob - '/**/*.resolver.ts'
. So for this file, you would name it user.resolver.ts
import { User } from './user.model';
@Resolver(User)
export class UserResolver extends BaseResolver<User> {
constructor(@InjectRepository(User) private readonly userRepository: Repository<User>) {
super(User, userRepository);
}
@Query(returns => [User])
async users(
@Args() { where, orderBy, limit, offset }: UserWhereArgs
): Promise<User[]> {
return this.find<UserWhereInput>(where, orderBy, limit, offset);
}
@Mutation(returns => User)
async createUser(@Arg('data') data: UserCreateInput, @Ctx() ctx: BaseContext): Promise<User> {
return this.create(data, ctx.user.id);
}
}
APP_HOST=localhost
APP_PORT=4100
TYPEORM_DATABASE=warthog
TYPEORM_USERNAME=postgres
TYPEORM_PASSWORD=
import 'reflect-metadata';
import { Server } from 'warthog';
async function bootstrap() {
const server = new Server();
return server.start();
}
bootstrap()
When you start your server, there will be a new generated
folder that has your GraphQL schema in schema.graphql
. This contains:
type User implements BaseGraphQLObject {
id: String!
createdAt: DateTime!
createdById: String!
updatedAt: DateTime
updatedById: String
deletedAt: DateTime
deletedById: String
version: Int!
name: String!
}
input UserCreateInput {
name: String!
}
enum UserOrderByInput {
createdAt_ASC
createdAt_DESC
updatedAt_ASC
updatedAt_DESC
deletedAt_ASC
deletedAt_DESC
name_ASC
name_DESC
}
input UserUpdateInput {
name: String
}
input UserWhereInput {
id_eq: String
id_in: [String!]
createdAt_eq: String
createdAt_lt: String
createdAt_lte: String
createdAt_gt: String
createdAt_gte: String
createdById_eq: String
updatedAt_eq: String
updatedAt_lt: String
updatedAt_lte: String
updatedAt_gt: String
updatedAt_gte: String
updatedById_eq: String
deletedAt_all: Boolean
deletedAt_eq: String
deletedAt_lt: String
deletedAt_lte: String
deletedAt_gt: String
deletedAt_gte: String
deletedById_eq: String
name_eq: String
name_contains: String
name_startsWith: String
name_endsWith: String
name_in: [String!]
}
input UserWhereUniqueInput {
id: String!
}
Notice how we've only added a single field on the model and you get pagination, filtering and tracking of who created, updated and deleted records automatically.
attribute | description | default |
---|---|---|
container | TypeDI container. Warthog uses dependency injection under the hood. | empty container |
authChecker | An instance of an AuthChecker to secure your resolvers. | |
autoGenerateFiles | Should the server auto-generate your schema, typings, etc... | true when NODE_ENV=development. false otherwise |
context | Context getter of form (request: Request) => object |
empty |
generatedFolder | Folder where generated content is created | process.cwd() + 'generated' |
logger | Logger | debug |
middlewares | Express middlewares to add to your server | none |
mockDBConnection | Opens a sqlite connection instead of Postgres. Helpful if you just need to generate the schema | false |
openPlayground | Should server open GraphQL Playground after starting? | false if running tests, true if in development mode |
port | App Server Port | 4000 |
resolversPath | Glob path to find your resolvers | process.cwd() + '/**/*.resolver.ts' |
All config is driven by environment variables. Most options can also be set by setting the value when creating your Server
instance.
variable | value | config option name | default |
---|---|---|---|
APP_HOST | App server host | appOptions.host | none |
APP_PORT | App server port | appOptions.port | 4000 |
TYPEORM_DATABASE | DB name | none | none |
TYPEORM_USERNAME | DB username | none | none |
TYPEORM_PASSWORD | DB password | none | none |
Warthog is intentionally opinionated to accelerate development and make use of technology-specific features:
- Postgres - currently the only database supported. This could be changed, but choosing Postgres allows adding a docker container and other goodies easily.
- Jest - other harnesses will work, but if you use Jest, we will not open the GraphQL playground when the server starts, for example.
- Soft deletes - no records are ever deleted, only "soft deleted". The base service used in resolvers filters out the deleted records by default.
Special thanks to the authors of:
Warthog is essentially a really opinionated composition of TypeORM and TypeGraphQL that uses similar GraphQL conventions to the Prisma project.
PRs accepted, fire away! Or add issues if you have use cases Warthog doesn't cover.
MIT © Dan Caddigan