In this post, we are going to talk a lot about asynchronous functions, something very fashionable nowadays, but not so much just a few years ago. With this, I do not mean that it is something necessarily new, but I believe that thanks to the JS ecosystem today the world happens in real-time. In this post, I want to teach the key concepts of this. But as always, we do not stay in theory and we are going to see a real implementation, such as real-time notifications from our application. I will try to be as brief and straightforward as possible. Definitions and concepts refers to the occurrence of events that happen in our program. They are executed independently of our main program, and never interfere with their execution. Prior to this, we were forced to wait for a response to continue execution, seriously impairing the user experience. Asynchronous programming: WebSockets represent a long-awaited evolution in client/server web technology. They allow a long-held single TCP socket connection to be established between the client and server which allows for bi-directional, full duplex, messages to be instantly distributed with little overhead resulting in a very low latency connection. WebSockets : . Click here to keep reading more about web sockets In other words, it allows us to establish a peer-to-peer connection between the client and the server. Before this, the client was only the one who knew where the server was, but not vice versa. Thanks to this, we can send a request to the server, and continue executing our program, without waiting for your response. Then the server knows where the client is, and can send you the response. Let's do it 👊 All of the above already makes sense for our notification system, right? Before continuing, make sure you have Redis installed. Sidekiq uses Redis to store all of its job and operational data. 👋 . If you do not know what Redis is, you can discover it on its official site helps us to work in the background in a super simple and efficient way. (Also, one of my favorite gems ♥️) Sidekiq I created for this article in order to focus directly on what interests us. The project is a simple blog with user authentication and the necessary front to display our notifications. You can download it and follow the article with me. this project you can see the full implementation in the "notifications" branch NOTE: Init configuration... In we will mount the routes of (framework for real-time communication over websockets) config/routes.rb ActionCable Rails.application.routes.draw mount ActionCable.server => do # everything else... '/cable' end Now, do you remember how WebSockets works? A peer-to-peer connection, well in other words, is also a _channel_ (as we call it in Rails), and within that channel, we have to always identify each user. This so that the server can know who to reply to, and know who made a request. In this case, we will identify it with the user.id (I am using devise) So, in : app/channels/application_cable/connection.rb identified_by .current_user = find_user user_id = cookies.signed[ ] current_user = User.find_by( user_id) current_user current_user reject_unauthorized_connection module ApplicationCable < ActionCable::Connection:: class Connection Base :current_user def connect self end def find_user "user.id" id: if else end end end end We are going to save the user who logged in a cookie (it will help us to obtain it from other places, we will see), an interesting solution for this (at least with Devise) is using Warden Hooks For that, we can create an initializer on our application, config/initializers/warden_hooks.rb Warden::Manager.after_set_user auth.cookies.signed[ ] = user.id auth.cookies.signed[ ] = .minutes.from_now Warden::Manager.before_logout auth.cookies.signed[ ] = auth.cookies.signed[ ] = do |user, auth, opts| "user.id" "user.expires_at" 30 end do |user, auth, opts| "user.id" nil "user.expires_at" nil end Now, let's create a table in our database to save every notification we create, for this, $ rails g model Notification user:references item:references viewed:boolean is a , I do it this way so they can add various types of notifications) NOTE: :item polymorphic association Let's specify this and other details in our migration ( ): db/migrate/TIMESTAMP_create_notifications.rb create_table t.references , t.references , t.boolean , t.timestamps < ActiveRecord::Migration[6.0] class CreateNotifications def change :notifications do |t| :user foreign_key: true :item polymorphic: true :viewed default: false end end end and, $ rails db:migrate In we are going to do a couple of things that we will see on the go. app/models/notification.rb belongs_to belongs_to , after_create { NotificationBroadcastJob.perform_later( ) } scope , ->{order( )} scope , ->{where( )} Notification.where( user_id).unviewed.count < ApplicationRecord class Notification :user :item polymorphic: true # Indicates a polymorphic reference self # We make this later :leatest "created_at DESC" :unviewed viewed: false # This is like a shortcut # This returns the number of unviewed notifications . def self for_user (user_id) user_id: end end Let's create a , remember that one of the most respected Rails philosophies is DRY (Don't Repeat Yourself), currently, each notification requires the same to work (in models)(again, in this project we only have publications, but We could have many other things that we want to integrate with our notification system, so with this form it is super simple). concern For that, app/models/concerns/notificable.rb extend ActiveSupport::Concern included has_many , after_commit .respond_to? NotificationSenderJob.perform_later( ) module Notificable # module '::' do # this appends in each place where we call this module :notifications as: :item :send_notifications_to_users end def send_notifications_to_users if self :user_ids # returns true if the model you are working with has a user_ids method self end end end Now we can include it in our . Remember that our expects the method to reply to you with the respective fix. Let's do that (app/models/post.rb): app/models/post.rb send_notifications_to_users user_ids Notificable belongs_to User.all.ids < ApplicationRecord class Post include :user def user_ids # send the notification to that users end end We are going to create the in charge of sending the notifications, this is what we will send to the background and we will handle with Sidekiq. For that, job $ rails g job NotificationSender Inside the job ( ): app/jobs/notification_sender_job.rb queue_as item.user_ids.each Notification.create( item, user_id) < ApplicationJob class NotificationSenderJob :default def perform (item) # this method dispatch when job is called do |user_id| item: user_id: end end end Finally, we need to install Sidekiq (and Sinatra to make few things easier). : Gemfile # everything ... gem , , gem , , else 'sinatra' '~> 2.0' '>= 2.0.8.1' 'sidekiq' '~> 6.0' '>= 6.0.7' Don't forget, $ bundle install We are going to tell Rails that we will use Sidekiq for jobs on the queue adapter ( ): config/application.rb # everything ... module Blog = :sidekiq end end else < :: # ... . . class Application Rails Application everything else config active_job queue_adapter We are also going to set up the routes that Sidekiq provides us, among them, a kind of backoffice for our background (later you can have access from localhost:3000/sidekiq), very interesting. In : config/routes.rb Rails.application.routes.draw # everything ... mount Sidekiq:: end require 'sidekiq/web' do else => Web '/sidekiq' Now we are going to create the channel through which we will transmit our notifications. $ rails g channel Notification In the backend of this channel ( ), we will subscribe users: app/channels/notification_channel.rb {current_user.id} < :: " .# class NotificationChannel ApplicationCable Channel def subscribed stream_from notifications " # in this way we identify to the user inside the channel later end def unsubscribed # Any cleanup needed when channel is unsubscribed end end And in the frontend of the channel ( ) it would be interesting to send a push notification to the browser, there are many JS libraries that make that very easy (like ), but in order not to make the post much heavier, we are going to print a simple message on the console. So: app/javascript/channels/notification_channel.js this consumer.subscriptions.create( , { received(data) { (data.action == ){ cosole.log( ) } } }); // everything else... "NotificationChannel" // everything else... if "new_notification" `New notification! Now you have unread notifications` ${data.message} // we will define action & message in the next step At this point, we already have a lot running, let's send that notification to the user! For this we are going to create another job that just does this, remember, the previous job is in charge of creating the notifications, this one does the broadcast. So, $ rails g job NotificationBroadcast Inside : app/jobs/notification_broadcast_job.rb = Notification.for_user(notification.user_id) ActionCable.server.broadcast , { : , : notification_count } end end < : ( ) class NotificationBroadcastJob ApplicationJob queue_as default def perform notification notification_count "notifications.#{ notification.user_id }" action "new_notification" message Fantastic, we already have everything working! 🎉 I'm going to add a few things to the backend to end the example. First of all, I'm going to add a method to my user model so I can count the notifications I haven't seen yet. And the model is a good place to do this query. In : app/models/user.rb < # ... . ( . ) class User ApplicationRecord everything else def unviewed_notifications_count Notification for_user self id end end I'm also going to create a controller, . Inside the controller ( ) i'm going to add some methods: $ rails g controller Notifications index app/controllers/notifications_controller.rb I will create a js view to be able to respond remote and display the latest notifications in my dropdown in the nav. In : app/helpers/notifications_helper.rb NotificationsHelper def render_notifications(notifications) notifications.map |notification| render partial: , :{ : notification} end.join( ).html_safe end end module do "notifications/#{notification.item_type.downcase}" locals notification "" Add the link in your nav. In my case ( ): app/views/partials/notifications.html.erb < , , { " " } %> %= link_to notifications_path remote: true data: type: script Let's not forget to add the paths ( ) for this new controller. app/config/routes.rb # everything ... Rails.application.routes.draw # everything ... resources :notifications, : [:index, :update] end else do else only Just create a partial for this item (like ). app/views/notifications/_post.rb They can include a link to 'mark as seen', in this way: <%= link_to notification_path(id: notification, notification: ), :put %> {viewed: true} : method To run this locally you will have to run Redis ( ) and Sidekiq ( ) + , have 3 terminal windows open with these 3 commands running in parallel. $ redis-server $ bundle exec sidekiq $ rails s That is all, I hope it is useful to you 👋 Also published at https://dev.to/matiascarpintini/real-time-notification-system-with-sidekiq-redis-and-devise-in-rails-6-33l9