From 47c4a02a0d44320128059047a0bd01482f2102a6 Mon Sep 17 00:00:00 2001 From: Paulo Margarido <64600052+paulomarg@users.noreply.github.com> Date: Tue, 3 Oct 2023 15:13:30 -0400 Subject: [PATCH] Simplify remix README file --- .changeset/funny-moons-judge.md | 2 + packages/shopify-app-remix/README.md | 297 +++------------------------ 2 files changed, 28 insertions(+), 271 deletions(-) create mode 100644 .changeset/funny-moons-judge.md diff --git a/.changeset/funny-moons-judge.md b/.changeset/funny-moons-judge.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/funny-moons-judge.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/shopify-app-remix/README.md b/packages/shopify-app-remix/README.md index dd31f22b8c..254b1bc5e1 100644 --- a/packages/shopify-app-remix/README.md +++ b/packages/shopify-app-remix/README.md @@ -12,11 +12,11 @@ Visit the [`shopify.dev` documentation](https://shopify.dev/docs/api/shopify-app ## Requirements -To follow these usage guides, you will need to: +To use this package, you will need to have: -- have a Shopify Partner account and development store -- have an app already set up on your partner account -- have a JavaScript package manager such as [yarn](https://yarnpkg.com) installed +- a Shopify Partner account and development store +- an app already set up on your partner account +- a JavaScript package manager such as [yarn](https://yarnpkg.com) installed ## Getting started @@ -43,11 +43,18 @@ Now let's install this package: npm install @shopify/shopify-app-remix ``` -Create `app/shopify.server.js`. We will use this file to configure our Shopify app: +Next, you'll need to set up some routes and headers so you can embed your app in the Shopify admin. +You can find more details on all the steps described here in the [package documentation](https://shopify.dev/docs/api/shopify-app-remix). + +Create `app/shopify.server.js`. We will use this file to configure our Shopify app by calling the [`shopifyApp` function](https://shopify.dev/docs/api/shopify-app-remix/latest/entrypoints/shopifyapp): ```ts // app/shopify.server.js +// Note that you don't need to import the node adapter if you're running on a different runtime. import '@shopify/shopify-app-remix/server/adapters/node'; +// Memory storage makes it easy to set an app up, but should never be used in production. +import {MemorySessionStorage} from '@shopify/shopify-app-session-storage-memory'; + import {LATEST_API_VERSION, shopifyApp} from '@shopify/shopify-app-remix'; const shopify = shopifyApp({ @@ -56,21 +63,11 @@ const shopify = shopifyApp({ appUrl: process.env.SHOPIFY_APP_URL!, scopes: ['read_products'], apiVersion: LATEST_API_VERSION, + sessionStorage: new MemorySessionStorage(), }); export default shopify; ``` -A description of these config options: - -| option | description | -| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| apiKey | The Client ID for your app. Copy it from your app in the Shopify partners dashboard. This is public. | -| apiSecretKey | The API Secret for your app. Copy it from your app in the Shopify partners dashboard. This is private. Do not commit this. | -| appUrl | This is the URL for your app. | -| scopes | What permissions your app needs. [More information](https://shopify.dev/docs/api/usage/access-scopes). | -| apiVersion | What versions of the [Admin API's](https://shopify.dev/docs/api/) do you want to use. If you are creating anew app use LATEST_API_VERSION. | -| restResources | What version of the [Shopify Admin REST API](https://shopify.dev/docs/api/admin-rest) do you want to use. If you are creating anew app use LATEST_API_VERSION. | - This will require some environment variables. So let's create an `.env` file: ```env @@ -79,19 +76,8 @@ SHOPIFY_API_SECRET="[Copy from partners dashboard]" SHOPIFY_APP_URL="[The tunnel URL you are using to run your app]" ``` -`shopifyApp` needs to reserve a [splat route](https://remix.run/docs/en/main/guides/routing#splats). The default is `apps/routes/auth/$.tsx`, but you can configure this route using the `authPathPrefix option`: - -```ts -// app/shopify.server.js -import {shopifyApp} from '@shopify/shopify-app-remix'; - -const shopify = shopifyApp({ - // ... - authPathPrefix: '/auth', -}); -``` - -Now let's create the [splat route](https://remix.run/docs/en/main/guides/routing#splats) for auth. It should export a loader that uses `shopifyApp` to authenticate: +`shopifyApp` needs to reserve a [splat route](https://remix.run/docs/en/main/guides/routing#splats) for auth. +It should export a loader that uses `shopifyApp` to authenticate: ```ts // app/routes/auth/$.tsx @@ -106,7 +92,7 @@ export async function loader({request}: LoaderArgs) { } ``` -Next, set up the `AppProvider` component in your app's routes. To do this pass the `process.env.SHOPIFY_API_KEY` to the frontend via the loader. +Next, set up the [`AppProvider` component](https://shopify.dev/docs/api/shopify-app-remix/latest/entrypoints/appprovider) in your app's routes. To do this pass the `process.env.SHOPIFY_API_KEY` to the frontend via the loader. Here is an example: @@ -168,257 +154,26 @@ export default function handleRequest( } ``` -## Setting up for your runtime - -By default, this package will work with the runtimes supported by [Remix adapters](https://remix.run/docs/en/1.17.1/other-api/adapter#official-adapters) because it relies on the same [Web Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). +### Running your app -Since Node.js doesn't fully implement that API, apps will need to import an extra adapter to set it up before using this package's exports. - -In the [Getting Started](#getting-started) section above, you'll notice that the example runs - -```ts -import '@shopify/shopify-app-remix/server/adapters/node'; -``` - -before calling `shopifyApp`. -If you're running on a runtime other than Node, then you can simply omit that line. - -## Loading your app in admin - -To load your app within the Shopify Admin, you need to: +To run your app and load it within the Shopify Admin, you need to: 1. Update your app's URL in your Partners Dashboard app setup page to `http://localhost:8080` 1. Update your app's callback URL to `http://localhost:8080/api/auth/callback` in that same page 1. Go to **Test your app** in Partners Dashboard and select your development store -## Authenticating admin requests - -`shopifyApp` provides methods for authenticating admin requests. -To authenticate admin requests you can call `shopify.authenticate.admin(request)` in a loader or an action: - -```ts -// app/routes/**/*.tsx -export const loader = async ({request}: LoaderArgs) => { - await shopify.authenticate.admin(request); - - return null; -}; -``` - -If there is a session for this user, this loader will return null. -If there is no session for the user, the loader will throw the appropriate redirect Response. - -> **Note**: If you are authenticating more than one route, we recommend using [Remix layout routes](https://remix.run/docs/en/1.18.1/file-conventions/routes-files#layout-routes) to automatically authenticate them. - -## Authenticating cross-origin admin requests - -If your Remix server is authenticating an admin extension, a request from the extension to Remix is cross-origin. Here `shopify.authenticate.admin` provides a cors function to add the required cross-origin headers: - -```ts -export const loader = async ({request}: LoaderArgs) => { - const {cors} = await shopify.authenticate.admin(request); - - return cors(json({my: 'data'})); -}; -``` - -### Headers - -It's important to note that the authentication functions in this package rely on throwing `Response` objects, which must be handled in your Remix routes using them. - -To do that, you can set up a [Remix `ErrorBoundary`](https://remix.run/docs/en/main/guides/errors). -We provide some abstractions for the error and headers boundaries to make it easier for apps to set those up. - -```ts -// app/routes/**/*.tsx -import {boundary} from '@shopify/shopify-app-remix'; - -export function ErrorBoundary() { - return boundary.error(useRouteError()); -} - -export const headers = (headersArgs) => { - return boundary.headers(headersArgs); -}; -``` - -> **Note**: You can also add this to a layout if you want to authenticate more than one route. - -### Using the Shopify admin GraphQL API - -To access the [Shopify Admin GraphQL API](https://shopify.dev/docs/api/admin-graphql) pass a request from a loader or an action to `shopify.authenticate.admin`. -This will either redirect the merchant to install your app or it will give you access to API functions. -E.g: - -```ts -// routes/**/*.tsx -import shopify from '../shopify.server'; -import {ActionArgs, json} from '@remix-run/node'; - -export async function action({request}: ActionArgs) { - const {admin} = await shopify.authenticate.admin(request); - - const response = await admin.graphql( - `#graphql - mutation populateProduct($input: ProductInput!) { - productCreate(input: $input) { - product { - id - } - } - }`, - { - variables: { - input: { - title: 'New product', - variants: [{price: 100}], - }, - }, - }, - ); - const parsedResponse = await response.json(); - - return json({data: parsedResponse.data}); -} -``` - -### Using the Shopify admin REST API - -`shopify.authenticate.admin` can returns methods for interacting with [Shopify Admin REST API](https://shopify.dev/docs/api/admin-rest). To access the [Shopify Admin REST API](https://shopify.dev/docs/api/admin-rest) first configure `shopifyApp` with the REST resources you would like to use: - -```ts -// app/routes/**/*.tsx -import {restResources} from '@shopify/shopify-api/rest/admin/2023-04'; - -const shopify = shopifyApp({ - restResources, - // ...etc -}); -``` - -Next pass a request to `shopify.authenticate.admin` in a loader or an action. This will either redirect the merchant to install your app or it will give you access to API functions. E.g: - -```ts -// app/routes/**/*.tsx -export const loader = async ({request}: LoaderArgs) => { - const {admin, session} = await shopify.authenticate.admin(request); - const data = await admin.rest.resources.Product.count({session}); - - return json(data); -}; -``` - -## Authenticating webhook requests - -Your app must respond to [mandatory webhook topics](https://shopify.dev/docs/apps/webhooks/configuration/mandatory-webhooks). In addition, your app can register [optional webhook topics](https://shopify.dev/docs/api/admin-rest/2023-04/resources/webhook#event-topics). - -To setup webhooks first we need to configure `shopifyApp` with 2 pieces: - -1. The webhooks you want to subscribe to. In this example we subscribe to the `APP_UNINSTALLED` topic. -2. The code to register the `APP_UNINSTALLED` topic after a merchant installs you app. Here `shopifyApp` provides an `afterAuth` hook. - -```ts -// shopify.server.js -import {shopifyApp, DeliveryMethod} from '@shopify/shopify-app-remix'; - -const shopify = shopifyApp({ - apiKey: '1707326264fde5037c658n120626ce3f', - // ...etc - webhooks: { - APP_UNINSTALLED: { - deliveryMethod: DeliveryMethod.Http, - callbackUrl: '/webhooks', - }, - }, - hooks: { - afterAuth: async ({session}) => { - shopify.registerWebhooks({session}); - }, - }, -}); -``` - -Next you must add a route for each `callbackUrl` you pass. It should use the `shopify.authenticate.webhook` function to authenticate the request. For example: - -To do this, your app must authenticate the request. - -```ts -// routes/webhooks.tsx -import {ActionArgs} from '@remix-run/node'; - -import shopify from '../shopify.server'; -import db from '../db.server'; - -export const action = async ({request}) => { - const {topic, shop, session} = await authenticate.webhook(request); - - switch (topic) { - case 'APP_UNINSTALLED': - if (session) { - await db.session.deleteMany({where: {shop}}); - } - break; - case 'CUSTOMERS_DATA_REQUEST': - case 'CUSTOMERS_REDACT': - case 'SHOP_REDACT': - default: - throw new Response('Unhandled webhook topic', {status: 404}); - } - - throw new Response(); -}; -``` +## Next steps -## Authenticating public requests - -Your Remix app may need to authenticate requests coming from a public context. An example of this would be a checkout extension. Here is how: - -```ts -// e.g: routes/api.public.notes.tsx -import shopify from '../shopify.server'; -import {LoaderArgs, json} from '@remix-run/node'; -import {getNotes} from '~/models/notes'; - -export const loader = async ({request}: LoaderArgs) => { - const {sessionToken, cors} = await shopify.authenticate.public(request); - - // E.g: Get notes using the shops admin domain - return cors(json(await getNotes(sessionToken.iss))); -}; -``` +Once your app is up and running, you can start using this package to interact with Shopify APIs, webhooks and more. -This can be useful if your app exposes checkout or theme extensions and those extensions need to access data from your app. +Here are some guides to help you set up your app: -**Note:** These requests are cross-origin, so you must use the cross-origin helper returned from `shopify.authenticate.public`. - -## Session Storage - -When calling `shopifyApp`, you must pass in a `sesionStorage` to store sessions. -You can change this by passing a different Session Adaptor to `shopifyApp`. -To make this easy Shopify offers [some production ready session adaptors](https://github.com/Shopify/shopify-app-js/tree/main/packages). - -In this example we'll swap the default session adaptor for [Prisma](https://www.prisma.io/). - -Let's pass the [Prisma app session storage](https://github.com/Shopify/shopify-app-js/blob/main/packages/shopify-app-session-storage-prisma/README.md) adaptor to `shopifyApp`: - -```ts -// app/shopify.server.js -import {shopifyApp} from '@shopify/shopify-app-remix'; -import {PrismaSessionStorage} from '@shopify/shopify-app-session-storage-prisma'; -import {PrismaClient} from '@prisma/client'; - -const prisma = new PrismaClient(); -const storage = new PrismaSessionStorage(prisma); - -const shopify = shopifyApp({ - sessionStorage: storage, - // ... -}); -``` +- [Interacting with Shopify Admin](https://shopify.dev/docs/api/shopify-app-remix/latest/guide-admin) +- [Subscribing to webhooks](https://shopify.dev/docs/api/shopify-app-remix/latest/guide-webhooks) -Note that this requires a `schema.prisma` file as defined in the README for [Prisma app session storage](https://github.com/Shopify/shopify-app-js/tree/main/packages/shopify-app-session-storage-prisma). -Remember to [set up your migrations](https://www.prisma.io/docs/concepts/components/prisma-migrate/get-started) after creating the schema file! +You can also authenticate requests from surfaces other than the admin. +To see all supported methods, see [the `shopify.authenticate` object documentation](https://shopify.dev/docs/api/shopify-app-remix/latest/authenticate). ## Gotchas / Troubleshooting -If you're experiencing unexpected behaviors when using this package, check our [app template's documentation](https://github.com/Shopify/shopify-app-template-remix#gotchas--troubleshooting) for some common examples. +If you're experiencing unexpected behaviors when using this package, check our [app template's documentation](https://github.com/Shopify/shopify-app-template-remix#gotchas--troubleshooting) for the solution to common issues.