Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify remix README file #458

Merged
merged 1 commit into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/funny-moons-judge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
297 changes: 26 additions & 271 deletions packages/shopify-app-remix/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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({
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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:

Expand Down Expand Up @@ -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.