template

Rails API

A starting template for a Rails API with login/sign-up functionality, PostgreSQL, RSpec and Pundit.

Ruby on RailsPostgreSQLRSpecPundit
22nd Apr '22
 • 
5 min read

Repo

This template is a public repo on GitHub, and can be found here.

Specifications

This project template is setup with Ruby 3.3.0 and Rails 7.1.3.2, the Pundit authorization gem, and has a PostgreSQL database.

Testing has been setup with RSpec, including factory_bot, Faker and Database Cleaner Adapter for ActiveRecord.

It includes authentication setup with Rails' has_secure_password (see 'Authentication' and 'Endpoints' sections below).

It also includes linting with a custom setup of Rubocop, and a GitHub workflow which run two checks, tests and linting, on each push or pull request to master.

Setup

Feel free to clone this template or use it any way you see fit. However, the simplest way to get started is to:

  • Navigate to the template on GitHub.

  • Click 'Use this template'. Use this template button

  • On the next screen, fill-in a repository name and click 'Create repository from template'. Create a new repository

  • On the next page, click the 'Code' button, and in the dropdown, copy the url beneath 'HTTPS'. Copy the URL

  • In your local terminal, CD into the folder where you want to store the project. Then type git clone [THE URL YOU JUST COPIED], for example git clone https://github.com/jro31/my-new-project.git, and press Enter. Clone the repo

  • CD into the created repo, for example cd my-new-project. CD into the repo

  • To check that all specs are passing, run bundle exec rspec. You should get 0 failures. Run specs

    No failures

  • Run rails s to start the server. Then navigate to localhost:3001. If all is well, you should see {"status":"It's working"}. Run rails s

    It's working!

Notes

Development

  • Run the development server with rails s.
  • The default port is 3001.
  • Update the origins "http://localhost:3000" line of config/initializers/cors.rb with the development URL of your frontend.
  • Update both key values of config/initializers/session_store.rb with the name of your app.
  • Update all instances of rails_api_template in config/cable.yml, config/database.yml and config/environments/production.rb to the name of your app, for example:
    • config/cable/yml
      • Update channel_prefix: rails_api_template_production to channel_prefix: my_new_project_production
    • config/database.yml
      • Update database: rails_api_template_development to database: my_new_project_development
      • Update #username: rails_api_template to #username: my_new_project
      • Update database: rails_api_template_test to database: my_new_project_test
      • Update database: rails_api_template_production to database: my_new_project_production
      • Update username: rails_api_template to username: my_new_project
      • Update password: <%= ENV["RAILS_API_TEMPLATE_DATABASE_PASSWORD"] %> to password: <%= ENV["MY_NEW_PROJECT_DATABASE_PASSWORD"] %>
    • config/environments/production.rb
      • Update # config.active_job.queue_name_prefix = "rails_api_template_production" to # config.active_job.queue_name_prefix = "my_new_project_production"
    • This is necessary for nothing but brevity if you only ever use this template once. However, if you use it multiple times without updating these values (particularly those in config/database.yml), you will run into issues such as both apps will start using the same development database.

Production

  • Update the origins "https://myappurl.com" line of config/initializers/cors.rb with the production URL of your frontend.
  • Update the domain value of config/initializers/session_store.rb with the production URL of your API.

Authentication

This template includes authentication setup using has_secure_password. This includes:

  • A User model with email/password validations (and specs for these validations).

  • A registrations controller that includes a #create action:

    • This action allows a user to register (sign-up).
    • If successful, it returns the status :created (201), with a json that includes the user ID, and the user email.
    • If unsuccessful, it returns the status :unprocessable_entity (422) with an error message.
    • This action has a request spec.
  • A sessions controller that has three actions:

    • The #create action allows a user to login.

      • If they provide a correct email/password combo, it returns the status :created (201), with a json that includes the user ID, and the user email.
      • Otherwise it returns the status :unauthorized (401) with an error message.
    • The #logged_in action utilises the CurrentUserConcern to check if a user is logged-in.

      • If they are it returns logged_in: true and the user, if not it returns logged_in: false.
    • The #logout action allows a user to logout.

    • All actions have request specs.

Endpoints

GET http://localhost:3001/

  • Root, to check that the API is working.

POST http://localhost:3001/api/v1/registrations

  • To register a user.
  • Requires a user param containing an email, password and password_confirmation, for example:
fetch('http://localhost:3001/api/v1/registrations', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    user: {
      email: 'example@email.com',
      password: 'password',
      password_confirmation: 'password',
    },
  }),
  credentials: 'include',
});

POST http://localhost:3001/api/v1/sessions

  • To create a session (login a user).
  • Requires a user param containing an email and password, for example:
fetch('http://localhost:3001/api/v1/sessions', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    user: {
      email: 'example@email.com',
      password: 'password',
    },
  }),
  credentials: 'include',
});

GET http://localhost:3001/api/v1/logged_in

  • To check if a user is logged-in.
  • Example request:
fetch('http://localhost:3001/api/v1/logged_in', {
  credentials: 'include',
});

DELETE http://localhost:3001/api/v1/logout

  • To logout a user.
  • Example request:
fetch('http://localhost:3001/api/v1/logout', {
  method: 'DELETE',
  credentials: 'include',
});