A production-ready GraphQL API authentication/user management sub-graph. Built with Sequelize, Apollo GraphQL, & Express
MIT License
Authentication service/subgraph optimized for Sequelize ORM
copy .env.example
file as .env
cp .env.example .env
or pull one from dotenv-vault, if your team has one
npx dotenv-vault@latest pull --dotenvMe=YOUR-TEAM-DOTENV_ME
yarn docker:build
yarn docker:start -d
yarn docker:cli
npx babel-node src/scripts/createRoot
Create client
client_id
in their request headers.npx babel-node src/scripts/createApp
npx babel-node src/scripts/listApp
yarn seed
.env.test
filecp .env.example .env.test
yarn createdb:test
yarn test
docker compose -t usmansbk/simple-server:release . -f Dockerfile.production
docker push usmansbk/auth-service:release
The server makes use of AWS SES to send emails. Setup your SES account and add the following environment variables. Verify your development email and ensure you have this AWS IAM Policy.
MAIL_FROM=[email protected]
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_REGION=us-east-1
Check the email-templates docs on how to design email templates.
Follow the official Twilio documentation to setup your Twilio account and add your TWILIO_ACCOUNT_SID
, TWILIO_AUTH_TOKEN
, and TWILIO_PHONE_NUMBER
to your .env
file.
Create a Firebase project if you don't have one.
Go to APIs & Auth > Credentials in the Google Developers Console and copy your OAuth 2.0 Client IDs Web Client GOOGLE_CLIENT_ID
and GOOGLE_SECRET_KEY
.
Generate an OAuth2 API v2 id token from Google 0Auth 2.0 Playground to test.
FACEBOOK_APP_ACCESS_TOKEN
and FACEBOOK_APP_ID
env variablesWe upload files via REST
endpoints. Why not File Upload mutation?
To set up your S3
for file storage:
AWS_S3_BUCKET
to env
fileFollow these instructions to get your CLOUDFRONT_API_ENDPOINT
. We use Amazon CloudFront to provide a caching layer to reduce the cost of image process and the latency of subsequent image delivery. The CloudFront domain name provides cached access to the image handler API.
For a more complex filtering, we mimic the sequelize filter query. In order to filter by associations, we assume all associations are aliased (using the as
option). This alias must have corresponding field in your graphql type. Example:
If you define a User has-many
Task relationship like so,
User.hasMany(Task, { as: "tasks" });
you must define a tasks
field in your graphql User
type schema
type User {
tasks(filter: TaskFilter): TaskList!
}
Refer to the sequelize docs for more info on Operators
Our cursor-based pagination must adhere to a List
interface. This is similar to the relay-connection pagination. But unlike relay, we return our items
as a flat list.
# Example
type TaskList implements List {
items: [Task]!
totalCount: Int!
pageInfo: PageInfo!
}
We eager-load requested fields that have a matching association alias
in their corresponding model. Example: If we have a User has-one
Picture relationship:
User.hasOne(Picture, { as: "avatar" });
# This will eager-load the `avatar` association. Both user and avatar will be fetched in a single SQL query
query {
user {
id
name
avatar {
url
}
}
}
Eager-loading only works with Query
. Mutation
isn't supported
Nested cursor-paginated fields aren't eager-loaded, and hard to maintain in the frontend.
Paginated fields should be added to the root Query
for the reason above.
Segment allows us to collect data with different analytics tools. To setup our analytics, create a Segment account and add your SEGMENT_WRITE_KEY
to the .env
file.
We use "wrapping exceptions" technique to handle client generated errors. This allows us to take full control of the kind of errors we return, and easily translate them before sending to the end-users.
Internal server errors are logged to sentry. Create a Sentry account and add your SENTRY_DSN
to the .env
file.
We use Eslint AirBnB coding guidelines and import alias. All aliases are prefixed with a ~
. To add a new alias, update the jsconfig.json
, .eslintrc.js
, and babel.config.json
files. We also make use of Husky precommit hook to enforce standard.
Model specific logic should be moved to their associated data sources, and resolver errors should be handled using Wrapping Exception technique.