Modernizing Legacy Shopify Custom App (PHP 7.1 + REST API) to FastAPI + GraphQL + App Bridge Architecture: Looking for Best Practices and Migration Guidance

Hi Team,
Hope you are doing well!

I’m looking for guidance and recommendations from experienced Shopify app developers who have recently modernized legacy Shopify applications to align with Shopify’s latest app development standards.

Current Application Overview

We currently have a Shopify Custom App developed using:

  • PHP 7.1
  • Shopify REST APIs
  • Plain OAuth implementation
  • No Shopify App Bridge tokenization/authentication system
  • Custom loyalty platform integration

Current Business Use Cases

The application provides loyalty services to Shopify merchants and their customers, including:

  • Customer enrollment into a loyalty program
  • Customer loyalty profile management
  • Loyalty points balance retrieval
  • Reward catalog management
  • Reward redemption processing
  • Customer and Order webhook processing
  • Shopify Extension Only App support for loyalty-related customer experiences

The app mainly consumes customer and order data through webhooks and synchronizes loyalty information with an external loyalty platform.

Target Architecture

We are evaluating a complete modernization of the application with the following stack:

Backend

  • Python
  • FastAPI
  • Shopify GraphQL APIs

Shopify Integration

  • Shopify App Bridge
  • Session Token Authentication
  • Shopify Managed OAuth
  • Webhooks
  • Shopify Events
  • Extension-Only App capabilities
  • Modern Embedded App architecture

From an application architecture perspective, what are the major transformations required when moving from:

PHP 7.1 + REST API + Legacy OAuth to Fast API + GraphQL + App Bridge + Session Tokens + Modern Shopify App Architecture?

What architectural patterns are recommended today?
Currently, we use traditional OAuth and stored access tokens.

  1. What is the recommended authentication flow today?
  2. When should App Bridge Session Tokens be used versus Admin API Access Tokens?
  3. How should Fast API validate Shopify session tokens?
  4. Are there reference implementations in Python/Fast API for embedded Shopify apps?
  5. Would you recommend FastAPI for Shopify app development?
  6. Which libraries, SDKs, and frameworks are commonly used today?
  7. Are there Shopify-approved or community-supported Python/FastAPI starter kits?

Our objective is to transform a legacy Shopify loyalty application into a modern, scalable, Shopify-aligned platform while minimizing technical debt and ensuring compatibility with Shopify’s future roadmap.

Any architectural diagrams, migration experiences, GitHub repositories, reference implementations, or best-practice recommendations would be greatly appreciated.

Thank you in advance for your guidance and expertise!

Current Stack: PHP 7.1 + REST API + OAuth
Target Stack: Python (Fast API) + GraphQL + App Bridge + Session Tokens + Webhooks + Extensions + Event-Driven Architecture + Shopify Custom App

Looking forward to learning from the community’s experiences.

Regards,
Jitesh Sinha (AnnexCloud)

FastAPI is a reasonable choice here, especially now that Shopify has an official shopify-app-python package with a FastAPI quickstart. Treat the migration as six explicit boundaries:

  1. Embedded UI: load the current App Bridge CDN script. Obtain a fresh App Bridge ID token for each browser-to-FastAPI request. Shopify’s current docs use “ID token” for the short-lived JWT; “session token” is the older term.

  2. Backend request authentication: have FastAPI verify the HS256 signature plus exp, nbf, aud, iss, and dest before trusting a request. Exchange that ID token for an offline access token for webhooks and background jobs. Use online tokens only when an action must follow the current staff member’s permissions or attribution.

  3. Installation and scopes: move scopes and subscriptions into shopify.app.toml and use Shopify-managed installation. Store tokens per shop with the granted scopes and API version. Avoid maintaining a second custom OAuth state machine if token exchange covers the embedded flow.

  4. GraphQL boundary: put GraphQL operations behind domain services such as customers, orders, rewards, and webhooks. Pin an API version, centralize cost/throttle handling, use cursor pagination, and migrate one resource group at a time. Shopify marks the REST Admin API as legacy, so I would avoid carrying REST-shaped models into the new core.

  5. Webhook ingress: verify the HMAC against the raw request body before JSON parsing, deduplicate with X-Shopify-Webhook-Id, return 2xx quickly, and queue the real work. Make the consumers idempotent and add a periodic GraphQL reconciliation job for missed or delayed events. Keep Shopify as the source of truth for customer and order facts, and the loyalty platform as the source of truth for points and rewards. Correlation IDs plus an outbox pattern help prevent double redemptions.

  6. Data and compliance: inventory every customer and order field before requesting scopes. Customer and order data is protected customer data, and uninstall/data-request/redaction flows should be part of the migration test plan.

A practical cutover sequence is: catalog the current REST calls and webhooks, add contract tests around the loyalty connector, build GraphQL read parity, introduce the new webhook queue and reconciliation job, move the embedded UI and token exchange, then migrate writes resource by resource. Run the old and new read paths side by side long enough to reconcile counts and IDs before each cutover.

The official references I would start with are Shopify’s “Authenticate an embedded app without a template,” “Enable Shopify-managed installations,” “Verify webhook deliveries,” and “About REST to GraphQL migration,” plus the Shopify/shopify-app-python repository.

Hi @Annex_Cloud,

@TinyOpsStudio covered the auth boundaries well, so here are the things that bit us in production after the auth work was done.

The rate limit changes shape, not just size. REST gives you a request bucket; GraphQL charges a calculated query cost against a leaky bucket — 100 points/second on Standard, 1000 on Plus. Every response carries a throttleStatus in its extensions block with currentlyAvailable and restoreRate, so drive your client off that rather than a fixed sleep. Asking for fewer fields genuinely costs less, which is a new habit coming from REST.

For the initial loyalty backfill, do not paginate customers and orders. Run a bulk operation: bulkOperationRunQuery hands you a JSONL URL, and you can subscribe to the bulk_operations/finish webhook instead of polling.

Build the error taxonomy early. 500/502/503/504 are worth retrying with backoff, but 402 means a frozen shop and is permanent — retrying it just burns worker capacity. We learned that one the slow way.

And one dev-store gotcha: after you change scopes in the app config, the old grant stays cached. Uninstall and reinstall on the test store, or you will chase a phantom re-auth loop that is not in your code.