This is a boilerplate to build your next SaaS product. It's a RubyOnRails 7 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.
- Current ruby version
3.2.1
- Bundler version
2.4.13
- Rails version
7.0.4.3
- PostgreSQL Server as db connector
This boilerplate works like a charm with the following gems:
- pg
- devise
- devise_invitable
- graphql
- graphql-auth
- rack-cors
- rack_attack
- rails_admin
- cancancan
- image_processing
- mini_magick
- puma
- bootsnap
- friendly_id
- dotenv
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.
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.
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
.
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.
graphql-auth is a graphql/devise extension which uses JWT tokens for user authentication. It follows secure by default principle.
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/
.
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:
```ruby
scope { resources }
def resources
::User.accessible_by(current_ability)
end
```
Set a connection_type as return type to allow pagination:
```ruby
type Types::Users::UserType.connection_type, null: false
```
Set `order_by` as query option and define allowed order attributes:
```ruby
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:
```ruby
# 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](https://github.com/puma/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](https://pawelurbanek.com/uuid-order-rails).
### 8. Automatic model annotation
Annotates Rails/ActiveRecord Models, routes, fixtures, and others based on
the database schema. See [annotate_models gem](https://github.com/ctran/annotate_models).
Run `$ annotate` in project root folder to update annotations.
### 9. Abilities with CanCanCan
[CanCanCan](https://github.com/CanCanCommunity/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
](https://github.com/sferik/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](https://github.com/svenfuchs/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](https://github.com/tigrish/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
](https://github.com/starchow/rails_admin-i18n).
#### Testing Locales
How to test your locale files and how to find missing one read [this
](https://github.com/svenfuchs/rails-i18n#testing-your-locale-file).
### 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
](https://github.com/norman/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`:
```sh
$ rails g migration add_slug_to_resource slug:uniq
$ bundle exec rake db:migrate
```
Edit your model file as the following:
```ruby
class Company < ApplicationRecord
extend FriendlyId
friendly_id :name, use: :slugged
end
```
Replace traditional `Company.find(params[:id])` with `Company.friendly.find(params[:id])`��
```ruby
company = Company.friendly.find(params[:id])
```
### 14. Testing
We are using the wonderful framework [rspec](https://github.com/rspec/rspec).
The test suit also uses [factory_bot_rails](https://github.com/thoughtbot/factory_bot_rails)
for fixtures.
Run `rspec spec`
#### FactoryBot
To create mock data in your tests we are using [factory_bot](https://github.com/thoughtbot/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](https://github.com/faker-ruby/faker).
Caution: The created data is not uniq by default.
#### Shoulda Matchers
[Shoulda Matchers](https://github.com/thoughtbot/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](https://github.com/simplecov-ruby/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
```sh
$ open /coverage/index.html
```
### 15. Linter with Rubocop
We are using the wonderful [rubocop](https://github.com/rubocop-hq/rubocop-rails)
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 the
`config/database.yml` for your connection.
## 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!