As a developer working with Shopify's ecosystem, I recently built a multi-tenant SaaS application that synchronizes customer data between Shopify stores and external services. In this article, I'll share my experience and technical insights into creating a secure, scalable Shopify app using App Bridge.
Project Overview
Our application needed to:
- Handle multiple Shopify stores (multi-tenancy)
- Process customer data securely
- Provide a seamless embedded experience
- Manage OAuth flows and webhooks
- Handle billing subscriptions
Let's dive into how we accomplished these requirements using Rails 8 and Shopify's App Bridge.
Setting Up the Foundation
First, we set up our Rails application with the necessary Shopify integrations. Here's how our initial configuration looked:
ShopifyApp.configure do |config|
config.embedded_app = true
config.scope = "read_customers,write_customers"
config.after_authenticate_job = false
config.api_version = "2024-10"
config.shop_session_repository = "Shop"
config.webhooks = [
{ topic: "app/uninstalled", address: "webhooks/app_uninstalled" },
{ topic: "customers/create", address: "webhooks/customers_create" },
{ topic: "customers/update", address: "webhooks/customers_update" }
]
end
Multi-tenant Data Model
Our core data model revolves around the Shop
model, which handles multi-tenancy:
class Shop < ActiveRecord::Base
include ShopifyApp::ShopSessionStorageWithScopes
has_many :customers, dependent: :destroy
encrypts :api_key, deterministic: true
end
Embedded App Architecture
One of the key aspects was creating a seamless embedded experience. We achieved this through our layout configuration:
<%# app/views/layouts/embedded_app.html.erb %>
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://unpkg.com/@shopify/app-bridge@3.7.9"></script>
<script>
var AppBridge = window['app-bridge'];
var createApp = AppBridge.default;
var app = createApp({
apiKey: "<%= ShopifyApp.configuration.api_key %>",
host: "<%= @host %>",
forceRedirect: true
});
</script>
</head>
<body>
<%= yield %>
</body>
</html>
Secure Authentication Flow
We implemented authenticated controllers to ensure secure access:
class AuthenticatedController < ApplicationController
include ShopifyApp::EnsureHasSession
before_action :ensure_store_settings
private
def ensure_store_settings
redirect_to settings_path unless current_shop.setup_completed?
end
end
Webhook Processing
Handling webhooks securely was crucial for our app. Here's our webhook processing implementation:
class WebhooksController < ApplicationController
include ShopifyApp::WebhookVerification
def customers_create
shop = Shop.find_by(shopify_domain: params[:shop_domain])
if shop
shop.with_shopify_session do
process_customer_data(params)
end
end
head :ok
end
private
def process_customer_data(data)
CustomerProcessingJob.perform_later(
shop_id: shop.id,
customer_data: data
)
end
end
Background Job Processing
We used Solid Queue for reliable background processing:
class CustomerProcessingJob < ApplicationJob
def perform(shop_id:, customer_data:)
shop = Shop.find(shop_id)
shop.with_shopify_session do
customer = shop.customers.find_or_initialize_by(
shopify_customer_id: customer_data["id"]
)
customer.update!(
email: customer_data["email"],
first_name: customer_data["first_name"],
last_name: customer_data["last_name"]
)
end
end
end
Billing Integration
We implemented Shopify's billing API to handle subscriptions:
config.billing = ShopifyApp::BillingConfiguration.new(
charge_name: "App Subscription",
amount: 4.99,
interval: ShopifyApp::BillingConfiguration::INTERVAL_EVERY_30_DAYS,
trial_days: 7
)
User Interface with Tailwind CSS
We created a clean, responsive interface using Tailwind CSS:
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 pb-12 pt-20">
<div class="sm:flex sm:items-center">
<div class="sm:flex-auto">
<h1 class="text-base font-semibold leading-6 text-gray-900">
Customers
</h1>
<p class="mt-2 text-sm text-gray-700">
Manage your synchronized customers
</p>
</div>
</div>
<%= render "customer_list", customers: @customers %>
</div>
Lessons Learned
Throughout this project, I learned several valuable lessons:
Session Management: Always use Shopify's session tokens for authentication rather than storing raw access tokens.
Webhook Reliability: Implement idempotency in webhook processing to handle potential duplicate events.
Background Jobs: Use background jobs for any operations that might take more than a few seconds to complete.
Error Handling: Implement comprehensive error handling and logging, especially for webhook processing and API calls.
Security: Always encrypt sensitive data and never expose API keys in the frontend.
Conclusion
Building a multi-tenant Shopify app requires careful consideration of security, scalability, and user experience. By leveraging Rails, App Bridge, and modern development practices, we created a robust application that securely handles multiple stores and their data.
The combination of Shopify's App Bridge, Rails 8, and modern tools like Solid Queue and Tailwind CSS provided a solid foundation for building a scalable SaaS application.
Next Steps
If you're building a Shopify app, consider these recommendations:
- Start with a solid authentication and authorization system
- Implement webhook handling early in the development process
- Use background jobs for long-running tasks
- Plan for scalability from the beginning
- Follow Shopify's security best practices
Remember that building a multi-tenant application requires careful consideration of data isolation and security at every level of your application.
Happy Coding!
Originally published at sulmanweb.com
Top comments (0)