@druidjs/app
v0.5.8
Published
- [App Setup](#app-setup) - [App Concepts](#app-concepts) - [Database models](#database-models) - [Graphql typeDefs, resolvers, and scalars](#database-models) - [Server Context](#context) - [Putting it all together](#putting-it-all-together)
Downloads
29
Readme
druidjs/app
App Setup
Druid autloads your application logic based on preconfigured paths so that you can streamline your API setup.
Druid is built on top of apolo-server (for responding to Graphql requests) and objection.js (for querying your database). If you're not familar with the above libraries, we recommended scanning through their documentation first.
To get started with Druid, first, install the package:
npm install @druidjs/app
Then, initialize Druid. You must pass it a knex connection (the query builder Objection.js uses), which Druid will automatically bind to all your database models.
// app.js
import Druid from '@druidjs/app'
import * as knex from 'knex'
const connection = knex({
client: process.env.DB_CLIENT,
connection: {
host: process.env.DB_HOST,
port: process.env.DB_PORT,
database: process.env.DB_DATABASE,
user: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD
}
})
const app = new Druid(connection)
app.listen(4000)
With minimal code, your database models
, along with your graphql typeDefs
/resolvers
, can now all be autoloaded for you. By default, all modules are found under the following glob pattern: src/entities/**/*.{module}.{js,ts}
.
Here's an example of the default folder structure which we highly encourage:
/src
/entities # all entities to be autoloaded by Druid
/User
/ helpers # user specific helper methods
/ queries # user specific query methods
model.js
resolvers.js
scalars.js
typeDefs.gql
/ mixins # model mixins
/ helpers # app-wide utilities
app.js # your druid application
We also recommend keep only having modules autoloaded by Druid in the root of each entity. Any additional logic you can add inside folders, as shown above.
If you'd like to customize the folder structure, you can pass custom paths, along with other settings, as the second parameter to Druid. Here are the defaults:
new Druid(connection, {
appKey: process.env.APP_KEY, // Secret application key (required for authentation helpers)
path: '/graphql', // Graphql API endpoint
srcDir: './src', // Base path to all autoloaded modules
modulePaths: {
models: './entities/**/model.*(ts|js)',
scalars: './entities/**/scalars.*(ts|js)',
typeDefs: './entities/**/typeDefs.gql',
resolvers: './entities/**/resolvers.*(ts|js)'
}
})
App Concepts
In the next three sections, we briefly explain the core parts of a Druid application. Then we'll bring it all together with example code.
Database models
Your database models
are the internal components of your application and primary means for interacting with your database.
See the objection.js documentation to learn how to setup your models.
Druid will automatically bind the database connection
to all models, and inject them in the ctx
passed to your graphql resolvers.
All autoloaded models will be named based on the name of their parent directory. So, for example, a model found under User/model.js
, can be accessed inside a graphql resolver as ctx.db.User
.
Graphql typeDefs
, resolvers
, and scalars
Your graphql typeDefs
, resolvers
, and scalars
are the external components of your application and primary means for specifying how graphql clients will interact with your API.
See the apollo-server documentation to learn how to setup your graphql API.
Druid will merge all autloaded graphql logic into a single schema
which it will pass to your apollo server.
resolvers
and scalars
will be merged according to the name of their exports. So for resolvers you should export a Query
and Mutation
property, and for scalars
you should export the name of the specific scalar.
Server Context
A ctx
object is passed as the third paramter to all your graphql resolvers. This object will contain all your autloaded models
via ctx.db
, along authentication
helpers via ctx.auth
.
// TODO allow customization of druid context
ctx
API
db
, Object which contains all database related helpers.$connection
, original databsae connection- ....all autloaded models
auth
, Object which contains all authentication related helpers.generateToken
, Function to generate authentication token.getUser
, Function to get authenticated user. Will fail if user is not auhtenticated.getUserId
, Function to get autenticated user id. Will fail if user is not auhtenticated.maybeGetUser
, Function to get authenticated user. Will returnnull
if no user is authenticated.
Putting it all together
Here's a basic example which shows the different components of a Druid application, along with usage of the context object inside resolvers.
// User/model.js
import { Model } from 'objection'
export default class User extends Model {
static get tableName() {
return 'users'
}
}
// User/typeDefs.js
type Query {
authUser: User!
}
type Mutation {
registerUser(username: String!, password: String!): AuthUserData!
loginUser(username: String!, password: String!): AuthUserData!
}
type User {
id: Int!
username: String!
password: String!
}
type AuthUserData {
user: User!
token: String!
}
// User/resolvers.js
export const Query = {
async authUser (_, __, { auth }) {
const authUser = await auth.getUser()
return authUser
}
}
export const Mutation = {
async createUser (root, { username, password }, { db }) {
return db.User.query().insert({ username, password })
}
async registerUser (_, { username, email, password }, { auth, db }) {
const user = await db.User.query().insert({ username, email, password })
const token = auth.generateToken(user.id)
return { user, token }
},
async loginUser (_, { username, password }, { auth, db }) {
const user = await db.User.query().where('username', username)
await user.verifyPassword(password, user.password)
const token = auth.generateToken(user.id)
return { user, token }
}
}