Skip to content

bragamat/rails-api-graphql-devise-jwt-template

Repository files navigation

Rails 7 with devise, JWT, graphQL and CanCanCan.

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.

Versions

  • Current ruby version 3.2.1
  • Bundler version 2.4.13
  • Rails version 7.0.4.3
  • PostgreSQL Server as db connector

Dependencies

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

🚀 Quick start

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:

    ```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!