π Rails 6 boilerplate with devise, JWT, graphQL, CanCanCan and RailsAdmin.
This is a boilerplate to build your next SaaS product. It's a RubyOnRails 6 backend with authentication, GraphQL API, Roles & Ability management and a admin dashboard. It works nicely together with clients made with Angular, React, Vue.js, React.Native, Swift, Kotlin or any other client framework which implements the JSON Web Tokens philosophy.
Versions
- Current ruby version
2.6.x
- Bundler version
2.1.4
- Rails version
6.0.X
- PostgreSQL Server as db connector
Dependencies
This boilerplate works like a charm with the following gems:
- pg
- devise
- devise_invitable
- graphql
- graphql-auth
- graphql-errors
- rack-cors
- rack_attack
- rails_admin
- cancancan
- image_processing
- mini_magick
- puma
- bootsnap
- friendly_id
- dotenv
π Quick start
You can have a running backend in seconds on heroku:
or clone this repo:
git clone https://github.com/zauberware/rails-devise-graphql my-saas-backend
cd my-saas-backend
Clone env_sample
to .env for local development. We set it up with default rails 3000
and client 8000
ports:
cp env_sample .env
Install the bundle:
bundle install
Make sure the PostgreSQL is running on localhost. You may have to change your credentials under /config/database.yml
:
rake db:create
rake db:migrate
rake db:seed
Run the development server:
rails s
Download a GraphQL client like GraphiQL or others to access and test your API. Point the GraphQL IDE to http://0.0.0.0:3000/graphql
Note: Make sure that the .env
file is included in the root of your project and you have defined CLIENT_URL
and DEVISE_SECRET_KEY
. Read more about the JSON Web Token this. There are plenty of packages available.
π What's included?
1. Database
The app uses a PostgreSQL database. It implements the connector with the gem pg
. The app already includes a User
and a Company
model with basic setup. We see an Company
as a company with it's users. We did not add multi-tenancy to this app. If you want to do it by yourself check out the apartment gem.
2. Authentication
The app uses devise's logic for authentication. For graphQL API we use the JWT token, but to access the rails_admin backend we use standard devise views, but registration is excluded.
Change devise settings under config/initializers/devise.rb
and config/initializers/graphql_auth.rb
.
Invitations
Admins of a company can invite new users. The process is handled with devise_invitable
. We added a inviteUser
and acceptInvite
mutation to handle this process via graphql.
Like in the reset password process we redirect the users to the frontend domain and not to backend.
3. JSON Web Token
graphql-auth is a graphql/devise extension which uses JWT tokens for user authentication. It follows secure by default principle.
4. GraphQL
graphql-ruby is a Ruby implementation of GraphQL. Sadly it's not 100% open source, but with the free version allows you amazing things to do. See the Getting Started Guide and the current implementations in this project under app/graphql/
.
Filters, Sorting & Pagination
Our BaseResolver
class provides everything you need to achieve filter, sorting and pagination. Have a look at the resolver resolvers/users/users.rb
:
How to:
Include SearchObject
module in your resolver:
class Users < Resolvers::BaseResolver
include ::SearchObject.module(:graphql)
Define the scope for this resolver:
scope { resources }
def resources
::User.accessible_by(current_ability)
end
Set a connection_type as return type to allow pagination:
type Types::Users::UserType.connection_type, null: false
Set order_by
as query option and define allowed order attributes:
option :order_by, type: Types::ItemOrderType, with: :apply_order_by
def allowed_order_attributes
%w[email first_name last_name created_at updated_at]
end
Allow filtering with a custom defined filter object & define allowed filter attributes:
# inline input type definition for the advanced filter
class UserFilterType < ::Types::BaseInputObject
argument :OR, [self], required: false
argument :email, String, required: false
argument :first_name, String, required: false
argument :last_name, String, required: false
end
option :filter, type: UserFilterType, with: :apply_filter
def allowed_filter_attributes
%w[email first_name last_name]
end
Schema on production
We have disabled introspection of graphQL entry points here app/graphql/graphql_schema.rb
. Remove disable_introspection_entry_points
if you want to make the schema public accessible.
5. CORS
Protect your app and only allow specific domains to access your API. Set CLIENT_URL=
in .env
to your prefered client. If you need advanced options please change the CORS settings here config/initializers/cors.rb
.
6. App server
The app uses Puma as the web serber. It is a simple, fast, threaded, and highly concurrent HTTP 1.1 server for Ruby/Rack applications in development and production.
7. UUID
The app uses UUID as ids for active record entries in the database. If you want to know more about using uuid instead of integers read this article by pawelurbanek.com.
8. Automatic model annotation
Annotates Rails/ActiveRecord Models, routes, fixtures, and others based on the database schema. See annotate_models gem.
Run $ annotate
in project root folder to update annotations.
9. Abilities with CanCanCan
CanCanCan is an authorization library for Ruby and Ruby on Rails which restricts what resources a given user is allowed to access. We combine this gem with a role
field defined on user model.
Start defining your abilities under app/models/ability.rb
οΏ½.
10. Rails Admin
To access the data of your application you can access the rails_admin dashboard under route http://0.0.0.0:3000/admin
. Access is currently only allowed for users with super admin role.
If you want to give your admin interface a custom branding you can override sass variables or write your own css under app/assets/stylesheets/rails_admin/custom
.
Change rails_admin settings under config/initializers/rails_admin.rb
.
11. I18n
This app has the default language en
and already set a secondary language de
. We included the rails-i18n to support other languages out of the box. Add more languages under config/initializers/locale.rb
.
Setting locale
To switch locale just append ?locale=de
at the end of your url. If no locale
param was set it uses browser default language (request env HTTP_ACCEPT_LANGUAGE
). If this is unknown it takes the default language of the rails app.
Devise
For devise we use devise-i18n to support other languages.
Change translations under config/locales/devise
.οΏ½If you want to support more languages install them with rails g devise:i18n:locale fr
. (<-- installs French)
Rails Admin
To get translations for rails admin out of the box we use rails_admin-i18n.
Testing Locales
How to test your locale files and how to find missing one read this.
12. HTTP Authentication
For your staging environment we recommend to use a HTTP Auth protection. To enable it set env var IS_HTTP_AUTH_PROTECTED
to true
.
Set user with HTTP_AUTH_USER
and password with HTTP_AUTH_PASSWORD
.
We enable HTTP auth currently for all controllers. The ApplicationController
class includes the concern HttpAuth
. Feel free to change it.
13. Auto generated slugs
To provider more user friendly urls for your frontend we are using friendly_id to auto generate slugs for models. We have already implemented it for the Company
model. For more configuration see config/initializers/friendly_id.rb
.
To create a new slug field for a model add a field slug
:
$ rails g migration add_slug_to_resource slug:uniq
$ bundle exec rake db:migrate
Edit your model file as the following:
class Company < ApplicationRecord
extend FriendlyId
friendly_id :name, use: :slugged
end
Replace traditional Company.find(params[:id])
with Company.friendly.find(params[:id])
οΏ½οΏ½
company = Company.friendly.find(params[:id])
14. Testing
We are using the wonderful framework rspec. The test suit also uses factory_bot_rails for fixtures.
Run rspec spec
FactoryBot
To create mock data in your tests we are using factory_bot. The gem is fixtures replacement with a straightforward definition syntax, support for multiple build strategies (saved instances, unsaved instances, attribute hashes, and stubbed objects), and support for multiple factories for the same class (user, admin_user, and so on), including factory inheritance.
Faker
Create fake data easily with faker gem. Caution: The created data is not uniq by default.
Shoulda Matchers
Shoulda Matchers provides RSpec- and Minitest-compatible one-liners to test common Rails functionality that, if written by hand, would be much longer, more complex, and error-prone.
Simplecov
SimpleCov is a code coverage analysis tool for Ruby. It uses Ruby's built-in Coverage library to gather code coverage data, but makes processing its results much easier by providing a clean API to filter, group, merge, format, and display those results, giving you a complete code coverage suite that can be set up with just a couple lines of code.
Open test coverage results with
$ open /coverage/index.html
15. Linter with Rubocop
We are using the wonderful rubocop to lint and auto fix the code. Install the rubocop VSCode extension to get best experience during development.
16. Security with Rack Attack
See config/initializers/rack_attack.rb
file. We have defined a common set of rules to block users trying to access the application multiple times with wrong credentials, or trying to create a hundreds requests per minute.
To speed up tests add this to your .env.test
ATTACK_REQUEST_LIMIT=30
ATTACK_AUTHENTICATED_REQUEST_LIMIT=30
17. Sending emails
Set your SMTP settings with these environment variables:
SMTP_ADDRESS
SMTP_PORT
SMTP_DOMAIN
SMTP_USERNAME
SMTP_PASSWORD
SMTP_AUTH
SMTP_ENABLE_STARTTLS_AUTO
Have a look at config/environments/production.rb
where we set the config.action_mailer.smtp_settings
.
from: email
Set the email address for your ApplicationMailer
and devise emails with env var DEVISE_MAILER_FROM
.
18. Deployment
The project runs on every server with ruby installed. The only dependency is a PostgreSQL database. Create a block production:
in theconfig/database.yml
for your connection.
Heroku
Choose the one click installer or push a clone of this repo to heroku by yourself. We added a Profile
to the project and heroku run the release:
statement after deploying a new version. Heroku will automatically set the db settings for your project, so there is nothing to do in config/database.yml
.
Make sure all ENV vars are set and the database settings are valid.
Bitbucket Pipelines
If you want to use Bitbucket pipelines for CI you can use the sample file bitbucket-pipelines.yml
in the project root.
Make sure to set ENV vars $HEROKU_API_KEY
and $HEROKU_APP_NAME
in Bitbuckets pipeline settings. (Will appear after enabling pipelines for your project.)
The pipeline has 2 environments: staging and production. Staging pipeline is getting triggered in develop
branch. Production deploy triggered by master
branch.
It also triggers pipeline while opening a PR.
What's missing?
- Check & retest locked accounts
- Invite for users, inviteMutation & acceptInviteMutation
- Registration add more fields (Firstname, Last name)
- Tests for filter, sorting & pagination
- Security: brakeman and bundler-audit
Feel free to make feature request or join development!
Share this repo
Help us to get more attention to this project:
π Contributors, backers & sponsors
This project exists thanks to all the people who contribute.
Thank you to all our backers!
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. (Become a sponsor)
β€οΈ Code of Conduct
Please note that zauberware has a Code of Conduct. By participating in this project online or at events you agree to abide by its terms.
Author
Script: https://github.com/zauberware/rails-devise-graphql
Author website: https://www.zauberware.com