chore: reorganize docs apps (#7228)
* reorganize docs apps * add README * fix directory * add condition for old docs
This commit is contained in:
1 parent
224ebb2154
commit
4fe28f5a95
6187 files changed
+601447
-598226
No files matched your search
@@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: [
|
||||
"docs/content"
|
||||
],
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
API_URL=
|
||||
DOCS_ALGOLIA_INDEX_NAME=
|
||||
API_ALGOLIA_INDEX_NAME=
|
||||
ALGOLIA_API_KEY=
|
||||
ALGOLIA_APP_ID=
|
||||
AI_ASSISTANT_URL=
|
||||
AI_API_ASSISTANT_TOKEN=
|
||||
AI_WEBSITE_ID=
|
||||
AI_API_ASSISTANT_RECAPTCHA_SITE_KEY=
|
||||
CLOUDINARY_CLOUD_NAME=
|
||||
@@ -1,8 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ["docs/docusaurus"],
|
||||
parserOptions: {
|
||||
project: true,
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
# Dependencies
|
||||
/node_modules
|
||||
|
||||
# Production
|
||||
/build
|
||||
|
||||
# Generated files
|
||||
.docusaurus
|
||||
.cache-loader
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.yarn/*
|
||||
.yarn/install-state.gz
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
+1
-13
@@ -1,13 +1 @@
|
||||
# Medusa Documentation
|
||||
|
||||
The Medusa documentation website is built with Docusaurus v2. You can learn more about contributing [here](https://docs.medusajs.com/contribution-guidelines).
|
||||
|
||||
## Components and Theme
|
||||
|
||||
### Extended Theme Components
|
||||
|
||||
Components that extend themes from Docusaurus or other plugins are placed under `src/theme`.
|
||||
|
||||
### Medusa Docs Components
|
||||
|
||||
Components created specifically for the Medusa Docs are placed under the `src/componenets` directory.
|
||||
Directory of old Medusa documentation. You can find it on the [v1.x branch](https://github.com/medusajs/medusa/tree/v1.x)
|
||||
@@ -1 +0,0 @@
|
||||
{"id":"https://github.com/medusajs/medusa/releases/tag/v1.20.6","content":"v1.20.6 is out","isCloseable":true}
|
||||
@@ -1,4 +0,0 @@
|
||||
module.exports = {
|
||||
presets: [require.resolve("@docusaurus/core/lib/babel/preset")],
|
||||
plugins: [['@babel/plugin-proposal-decorators', { legacy: true }]],
|
||||
}
|
||||
@@ -1,368 +0,0 @@
|
||||
---
|
||||
description: "In this document, you'll learn about the different ways you can configure the admin dashboard."
|
||||
---
|
||||
|
||||
import { TypeList } from "docs-ui"
|
||||
|
||||
# Admin Configuration
|
||||
|
||||
In this document, you'll learn about the different ways you can configure the admin dashboard.
|
||||
|
||||
## Plugin Options
|
||||
|
||||
The plugin accepts the following options:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: "@medusajs/admin",
|
||||
/** @type {import('@medusajs/admin').PluginOptions} */
|
||||
options: {
|
||||
serve: true,
|
||||
autoRebuild: true,
|
||||
backend: "https://example.com",
|
||||
path: "/app",
|
||||
outDir: "build",
|
||||
develop: {
|
||||
open: true,
|
||||
port: 7001,
|
||||
host: "example.com",
|
||||
logLevel: "error",
|
||||
stats: "normal",
|
||||
allowedHosts: "auto",
|
||||
webSocketURL: undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
<TypeList types={[
|
||||
{
|
||||
name: "serve",
|
||||
type: "boolean",
|
||||
optional: true,
|
||||
defaultValue: "true",
|
||||
description: "Whether to serve the admin dashboard when the Medusa backend starts. If set to `false`, you can serve the admin dashboard using the [dev command](#develop-command-options).",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "autoRebuild",
|
||||
type: "boolean",
|
||||
optional: true,
|
||||
defaultValue: "false",
|
||||
description: "Whether the admin UI should be rebuilt if there are any changes or if a missing build is detected when the backend starts. If not set, you must [manually build the admin dashboard](#build-command-options).",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "backend",
|
||||
type: "string",
|
||||
optional: true,
|
||||
defaultValue: "",
|
||||
description: "The URL of the Medusa backend. Its value is only used if the `serve` option is set to `false`.",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "path",
|
||||
type: "string",
|
||||
optional: true,
|
||||
defaultValue: "/app",
|
||||
description: "The path the admin server should run on when running the Medusa backend in production. It must be prefixed with a slash `/`, but it can't end with a `/`, which throws an error. It also can't be one of the reserved paths: \"admin\" and \"store\".",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "outDir",
|
||||
type: "string",
|
||||
optional: true,
|
||||
defaultValue: "",
|
||||
description: "The directory to output the admin build to. By default, the plugin builds the admin to the `build` directory in the root of the Medusa backend directory.",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "develop",
|
||||
type: "object",
|
||||
optional: true,
|
||||
defaultValue: "",
|
||||
description: "Options for the admin development server.",
|
||||
expandable: false,
|
||||
children: [
|
||||
{
|
||||
name: "open",
|
||||
type: "boolean",
|
||||
optional: true,
|
||||
defaultValue: "true",
|
||||
description: "Whether the browser should be opened when the admin development server starts.",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "port",
|
||||
type: "number",
|
||||
optional: true,
|
||||
defaultValue: "7001",
|
||||
description: "The port the admin dashboard runs on.",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "host",
|
||||
type: "string",
|
||||
optional: true,
|
||||
defaultValue: "localhost",
|
||||
description: "The host of the backend. This is useful if you're running a development server remotely.",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "logLevel",
|
||||
type: "\"error\" \\| \"none\" \\| \"warn\" \\| \"info\" \\| \"log\" \\| \"verbose\"",
|
||||
optional: true,
|
||||
defaultValue: "error",
|
||||
description: "The log level of the admin development server.",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "stats",
|
||||
type: "\"normal\" \\| \"debug\"",
|
||||
optional: true,
|
||||
defaultValue: "normal",
|
||||
description: "The verbosity of the admin development server.",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "allowedHosts",
|
||||
type: "\"auto\" \\| \"all\" \\| string[]",
|
||||
optional: true,
|
||||
defaultValue: "auto",
|
||||
description: "The development server's allowed hosts.",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "webSocketURL",
|
||||
type: "string \\| object \\| undefined",
|
||||
optional: true,
|
||||
defaultValue: "",
|
||||
description: "The URL to a web socket server",
|
||||
expandable: false,
|
||||
children: [
|
||||
{
|
||||
name: "hostname",
|
||||
type: "string",
|
||||
optional: true,
|
||||
defaultValue: "",
|
||||
description: "The web socket's hostname.",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "password",
|
||||
type: "string",
|
||||
optional: true,
|
||||
defaultValue: "",
|
||||
description: "The web socket's password.",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "path name",
|
||||
type: "string",
|
||||
optional: true,
|
||||
defaultValue: "",
|
||||
description: "The web socket's path name.",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "port",
|
||||
type: "string",
|
||||
optional: true,
|
||||
defaultValue: "",
|
||||
description: "The web socket's port.",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "username",
|
||||
type: "string",
|
||||
optional: true,
|
||||
defaultValue: "",
|
||||
description: "The web socket's username.",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Admin CLI
|
||||
|
||||
### Build Command Options
|
||||
|
||||
The `build` command in the admin CLI allows you to manually build the admin dashboard. If you intend to use it, you should typically add it to the `package.json` of the Medusa backend:
|
||||
|
||||
```json title="package.json"
|
||||
{
|
||||
"scripts": {
|
||||
// other scripts...
|
||||
"build:admin": "medusa-admin build"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can add the following option to the `medusa-admin build` command:
|
||||
|
||||
- `--deployment`: a boolean value indicating that the build should be ready for deployment. When this option is added, options are not loaded from `medusa-config.js` anymore, and it means the admin will be built to be hosted on an external host. This also means that the backend URL is loaded from the `MEDUSA_ADMIN_BACKEND_URL` environment variable. For example, `medusa-admin build --deployment`.
|
||||
|
||||
### Develop Command Options
|
||||
|
||||
The `develop` command in the admin CLI allows you to run the admin dashboard in development separately from the Medusa backend. If you intend to use it, you should typically add it to the `package.json` of the Medusa backend:
|
||||
|
||||
```json title="package.json"
|
||||
{
|
||||
"scripts": {
|
||||
// other scripts...
|
||||
"dev:admin": "medusa-admin develop"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can add the following options to the `medusa-admin develop` command:
|
||||
|
||||
- `--backend` or `-b`: a string specifying the URL of the Medusa backend. By default, it's the value of the environment variable `MEDUSA_ADMIN_BACKEND_URL`. For example, `medusa-admin develop --backend example.com`.
|
||||
- `--port` or `-p`: the port to run the admin on. By default, it's `7001`. For example, `medusa-admin develop --port 8000`.
|
||||
|
||||
---
|
||||
|
||||
## Change Backend URL
|
||||
|
||||
### In Development - Disabled Serve
|
||||
|
||||
To change the backend's URL that the admin sends request to in development when the `serve` option is disabled, set the `backend` option to the URL of your Medusa backend.
|
||||
|
||||
For example:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: "@medusajs/admin",
|
||||
/** @type {import('@medusajs/admin').PluginOptions} */
|
||||
options: {
|
||||
serve: false,
|
||||
backend: "http://localhost:9001",
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### In Development - Enabled Serve
|
||||
|
||||
To change the backend's URL when the `serve` option is enabled, set the `host` and `port` properties of the `develop` option to those of the Medusa backend. This is useful when running Medusa remotely in development, such as using GitHub Codespace.
|
||||
|
||||
For example:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: "@medusajs/admin",
|
||||
/** @type {import('@medusajs/admin').PluginOptions} */
|
||||
options: {
|
||||
// other options...
|
||||
develop: {
|
||||
port: 7000,
|
||||
host: "example.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### In Production
|
||||
|
||||
:::note
|
||||
|
||||
This assumes that you've deployed the admin separately and you're passing the `--deployment` option to the [build command](#build-command-options).
|
||||
|
||||
:::
|
||||
|
||||
To change the backend's URL that the admin sends request to in production, set the environment variable `MEDUSA_ADMIN_BACKEND_URL` to the backend's URL.
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
MEDUSA_ADMIN_BACKEND_URL=https://example.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Custom Environment Variables
|
||||
|
||||
If you want to set environment variables that you want to access in your admin dashboard's customizations (such as in [widgets](./widgets.md) or [UI routes](./routes.md)), your environment variables must be prefixed with `MEDUSA_ADMIN_`. Otherwise, it won't be loaded within the admin.
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
MEDUSA_ADMIN_CUSTOM_API_KEY=123...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Custom Webpack Configurations
|
||||
|
||||
:::note
|
||||
|
||||
Plugins cannot include webpack customizations.
|
||||
|
||||
:::
|
||||
|
||||
The admin dashboard uses [Webpack](https://webpack.js.org/) to define the necessary configurations for both the core admin plugin and your extensions. So, for example, everything works out of the box with Tailwind CSS, the admin dependencies, and more.
|
||||
|
||||
However, you may have some advanced case where you need to tweak the webpack configurations. For example, you want to support styling your extensions with CSS Modules.
|
||||
|
||||
For such use cases, you can extend the default webpack configurations defined in the admin plugin to add your custom configurations.
|
||||
|
||||
To do that, create the file `src/admin/webpack.config.js` that uses the `withCustomWebpackConfig` method imported from `@medusajs/admin` to export the extended configurations. The method accepts a callback function that must return an object of [webpack configuration](https://webpack.js.org/configuration/). The callback function accepts two parameters:
|
||||
|
||||
1. `config`: the first parameter is an object that holds the default webpack configuration. You should add your configurations to this object, then return it. Not returning the default configurations will lead to the application breaking.
|
||||
2. `webpack`: the second parameter is the webpack instance.
|
||||
|
||||
:::warning
|
||||
|
||||
This is an advanced feature and requires knowledge of configuring webpack. If configured wrongly, it may lead to the admin application breaking.
|
||||
|
||||
:::
|
||||
|
||||
For example:
|
||||
|
||||
```js title="src/admin/webpack.config.js"
|
||||
import { withCustomWebpackConfig } from "@medusajs/admin"
|
||||
|
||||
export default withCustomWebpackConfig((config, webpack) => {
|
||||
config.plugins.push(
|
||||
new webpack.DefinePlugin({
|
||||
"process.env": {
|
||||
NODE_ENV: JSON.stringify("production"),
|
||||
API_URL:
|
||||
JSON.stringify("https://api.medusa-commerce.com"),
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
return config
|
||||
})
|
||||
```
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,177 +0,0 @@
|
||||
---
|
||||
description: "Learn how to install Medusa's admin dashboard. The admin dashboard gives merchants an easy-to-use interface to manage their data such as orders, products, regions, and more."
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Feedback from '@site/src/components/Feedback';
|
||||
import DetailsList from '@site/src/components/DetailsList'
|
||||
import AdminLoginSection from '../troubleshooting/signing-in-to-admin.md'
|
||||
import CorsSection from '../troubleshooting/cors-issues.md'
|
||||
|
||||
# Admin Dashboard Quickstart
|
||||
|
||||
This document will guide you through setting up the admin dashboard in the Medusa backend.
|
||||
|
||||
## Overview
|
||||
|
||||
The admin dashboard is installed on the Medusa backend. The admin dashboard starts when you start the Medusa backend. This also means you can later deploy the Medusa backend along with the admin dashboard on the same hosting.
|
||||
|
||||
This guide will explain the steps and configurations required to set up the admin dashboard.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Backend
|
||||
|
||||
As the admin dashboard is a plugin installed on the Medusa Backend, you must have a Medusa Backend installed first. You can learn how to install it in [this documentation](../development/backend/install.mdx).
|
||||
|
||||
### Node.js
|
||||
|
||||
The Admin uses [Vite v4.1.4](https://vitejs.dev/guide/#scaffolding-your-first-vite-project) which requires v14.8+ or v16+ of Node.js, and as Medusa requires v16 or greater it's recommended you use v16+ of Node.js.
|
||||
|
||||
You can check which version of Node you have by running the following command:
|
||||
|
||||
```bash noReport
|
||||
node -v
|
||||
```
|
||||
|
||||
You can install Node from the [official website](https://nodejs.org/en/).
|
||||
|
||||
---
|
||||
|
||||
## Install and Serve Admin with the Backend
|
||||
|
||||
This section explains how to install the admin to be served with the Medusa Backend and later deployed together.
|
||||
|
||||
### Step 1: Install the Package
|
||||
|
||||
In the directory of your Medusa backend, run the following command to install admin dashboard:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install @medusajs/admin
|
||||
```
|
||||
|
||||
### Step 2: Add Admin to Medusa Configurations
|
||||
|
||||
In `medusa-config.js`, add the admin plugin into the array of `plugins`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: "@medusajs/admin",
|
||||
/** @type {import('@medusajs/admin').PluginOptions} */
|
||||
options: {
|
||||
// ...
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
Check [this documentation](./configuration.mdx#plugin-options) for a full list of available options.
|
||||
|
||||
### Step 3: Test the Admin Dashboard
|
||||
|
||||
You can test the admin dashboard by running the following command in the directory of the Medusa backend:
|
||||
|
||||
```bash
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
This starts the Medusa Backend and the admin dashboard in a development environment. By default, the admin will be available on the URL `localhost:7001` and the browser will open automatically to the admin dashboard in your default browser, unless you have the `develop.open` option disabled.
|
||||
|
||||
<Feedback
|
||||
event="survey_admin_quickstart"
|
||||
question="Did you set up the admin successfully?"
|
||||
positiveQuestion="Is there anything that should improved?"
|
||||
negativeQuestion="Please describe the issue you faced."
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## Production Path
|
||||
|
||||
:::note
|
||||
|
||||
This doesn't apply if you have the `serve` option disabled or you're deploying the admin separately.
|
||||
|
||||
:::
|
||||
|
||||
When you run the Medusa project in a production environment (such as with the `npx medusa start` command), the admin dashboard will be available at `<MEDUSA_URL>/<ADMIN_PATH>`, where:
|
||||
|
||||
1. `<MEDUSA_URL>` is the URL of your Medusa backend. By default, it'll be `localhost:9000` locally.
|
||||
2. `<ADMIN_PATH>` is the path you define in the [admin plugin's configurations](./configuration.mdx#plugin-options).
|
||||
|
||||
So, if you simulate a production environment locally, the admin dashboard will run by default on `localhost:9000/app`.
|
||||
|
||||
---
|
||||
|
||||
## Demo Credentials
|
||||
|
||||
If you installed the demo data when you installed the Medusa backend by running:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run seed
|
||||
```
|
||||
|
||||
You can use the email `admin@medusa-test.com` and password `supersecret` to log in.
|
||||
|
||||
:::info
|
||||
|
||||
Passwords in Medusa are hashed using the [scrypt-kdf](https://www.npmjs.com/package/scrypt-kdf). The password hash is then stored in the database.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Create a New Admin User
|
||||
|
||||
To create a new admin user from the command line, run the following command in the directory holding your Medusa backend:
|
||||
|
||||
```bash
|
||||
npx medusa user -e some@email.com -p some-password
|
||||
```
|
||||
|
||||
This will create a new user that you can use to log into your admin panel.
|
||||
|
||||
---
|
||||
|
||||
## Admin User Guide
|
||||
|
||||
The admin dashboard provides a lot of ecommerce features including managing Return Merchandise Authorization (RMA) flows, store settings, products, orders, and much more.
|
||||
|
||||
You can learn more about the admin dashboard and its features in the [User Guide](../user-guide.mdx).
|
||||
|
||||
---
|
||||
|
||||
## Multi-Language Support
|
||||
|
||||
Medusa supports multiple languages and translations. Check out available languages [here](../user-guide/tips/languages.md).
|
||||
|
||||
Refer to [this user guide](../user-guide/users/profile.md#change-admin-language) to learn how to switch the language of the Medusa Admin.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting Installation
|
||||
|
||||
<DetailsList
|
||||
sections={[
|
||||
{
|
||||
title: 'Signing into Admin',
|
||||
content: <AdminLoginSection />
|
||||
},
|
||||
{
|
||||
title: 'CORS Errors',
|
||||
content: <CorsSection />
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Admin Configuration](./configuration.mdx)
|
||||
- [Admin widgets](./widgets.md)
|
||||
- [Admin UI routes](./routes.md)
|
||||
@@ -1,368 +0,0 @@
|
||||
---
|
||||
title: 'How to Create an Admin UI Route'
|
||||
description: 'Learn how to create a new route in the admin dashboard.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
In this document, you’ll learn how to create a new route in the admin dashboard.
|
||||
|
||||
## Overview
|
||||
|
||||
You can customize the admin dashboard that Medusa provides to add new routes. This is useful if you want to add new subpages to the admin dashboard, or you want to add new pages that appear in the sidebar as well.
|
||||
|
||||
An admin UI route is essentially a React Component created under the `src/admin/routes` directory.
|
||||
|
||||
This guide explains how to create a new route in the admin dashboard with some examples.
|
||||
|
||||
:::note
|
||||
|
||||
If you want to create a page under the Settings page, please refer to [this documentation](./setting-pages.md) instead.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
It’s assumed you already have a Medusa backend with the admin plugin installed before you move forward with this guide. If not, you can follow [this documentation page](../create-medusa-app.mdx) to install a Medusa project.
|
||||
|
||||
### (Optional) TypeScript Preparations
|
||||
|
||||
Since routes are React components, they should be written in `.tsx` or `.jsx` files. If you’re using Typescript, you need to make some adjustments to avoid Typescript errors in your Admin files.
|
||||
|
||||
This section provides recommended configurations to avoid any TypeScript errors.
|
||||
|
||||
:::note
|
||||
|
||||
These changes may already be available in your Medusa project. They're included here for reference purposes.
|
||||
|
||||
:::
|
||||
|
||||
First, update your `tsconfig.json` with the following configurations:
|
||||
|
||||
```json title="tsconfig.json"
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2019",
|
||||
"module": "commonjs",
|
||||
"allowJs": true,
|
||||
"checkJs": false,
|
||||
"jsx": "react-jsx",
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"noEmit": false,
|
||||
"strict": false,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/"],
|
||||
"exclude": [
|
||||
"dist",
|
||||
"build",
|
||||
".cache",
|
||||
"tests",
|
||||
"**/*.spec.js",
|
||||
"**/*.spec.ts",
|
||||
"node_modules",
|
||||
".eslintrc.js"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The important changes to note here are the inclusion of the field `"jsx": "react-jsx"` and the addition of `"build"` and `“.cache”` to `exclude`.
|
||||
|
||||
The addition of `"jsx": "react-jsx"` specified how should TypeScript transform JSX, and excluding `build` and `.cache` ensures that TypeScript ignores build and development files.
|
||||
|
||||
Next, create the file `tsconfig.server.json` with the following content:
|
||||
|
||||
```json title="tsconfig.server.json"
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
/* Emit a single file with source maps instead of having a separate file. */
|
||||
"inlineSourceMap": true
|
||||
},
|
||||
"exclude": ["src/admin", "**/*.spec.js"]
|
||||
}
|
||||
```
|
||||
|
||||
This is the configuration that will be used to transpile your custom backend code, such as services or entities. The important part is that it excludes `src/admin` as that is where your Admin code will live.
|
||||
|
||||
Finally, create the file `tsconfig.admin.json` with the following content:
|
||||
|
||||
```json title="tsconfig.admin.json"
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "esnext"
|
||||
},
|
||||
"include": ["src/admin"],
|
||||
"exclude": ["**/*.spec.js"]
|
||||
}
|
||||
```
|
||||
|
||||
This is the configuration that will be used when transpiling your admin code.
|
||||
|
||||
---
|
||||
|
||||
## Create the Admin UI Route
|
||||
|
||||
In this section, you’ll learn the basics of creating an admin UI route.
|
||||
|
||||
### Step 1: Create File
|
||||
|
||||
Custom admin UI routes are added under the `src/admin/routes` directory of your Medusa project. The path of the file depends on the path you want the route to be available under. It is based on [Next.js 13’s App Router](https://nextjs.org/docs/app/building-your-application/routing/defining-routes).
|
||||
|
||||
For example, if you want the route to be available in the admin dashboard under the path `/a/custom` you should create your admin route under the path `src/admin/routes/custom/page.tsx`.
|
||||
|
||||
:::tip
|
||||
|
||||
All admin routes are prefixed with `/a` by default.
|
||||
|
||||
:::
|
||||
|
||||
You can also create [dynamic routes](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes). For example, you can create the route `/a/custom/[id]` by creating an admin router under the path `src/admin/routes/custom/[id]/page.tsx`.
|
||||
|
||||
### Step 2: Create React Component in File
|
||||
|
||||
For an admin route to be valid, it must default export a React component. There are no restrictions on the content of the React component.
|
||||
|
||||
For example, you can create the file `src/admin/routes/custom/page.tsx` with the following content:
|
||||
|
||||
```tsx title="src/admin/routes/custom/page.tsx"
|
||||
const CustomPage = () => {
|
||||
return (
|
||||
<div>
|
||||
This is my custom route
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CustomPage
|
||||
```
|
||||
|
||||
This will create an admin UI route at the path `/a/custom`, with its content being the content of the React component.
|
||||
|
||||
### Step 3: Test it Out
|
||||
|
||||
To test your admin UI route, run the following command in the root directory of the Medusa backend project:
|
||||
|
||||
```bash npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
This will build your admin and opens a window in your default browser to `localhost:7001`. After you log in, if you go to `localhost:7001/a/custom`, you’ll find the page you just created.
|
||||
|
||||
:::note
|
||||
|
||||
When using the `develop` command, the admin dashboard will run in development mode and will restart whenever you make changes to your admin customizations. This allows you to see changes in the dashboard instantly during your development.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Route Props
|
||||
|
||||
Every route receives props of the type `RouteProps`, which includes the `notify` prop. The `notify` prop is an object that includes the following attributes:
|
||||
|
||||
- `success`: a function that can be used to show a success message.
|
||||
- `error`: a function that can be used to show an error message.
|
||||
- `warn`: a function that can be used to show a warning message.
|
||||
- `info`: a function that can be used to show an info message.
|
||||
|
||||
For example:
|
||||
|
||||
```tsx
|
||||
import { Post } from "../../../../../models/post"
|
||||
import PostForm from "../../../../components/post/form"
|
||||
import { RouteProps } from "@medusajs/admin"
|
||||
|
||||
const BlogPostCreatePage = ({
|
||||
notify,
|
||||
}: RouteProps) => {
|
||||
const onSuccess = (post: Post) => {
|
||||
notify.success(
|
||||
"Success",
|
||||
`Post ${post.title} created successfully`
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-xl mb-2">Create Post</h1>
|
||||
<PostForm onSuccess={onSuccess} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default BlogPostCreatePage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Show Route in Sidebar
|
||||
|
||||
You can add your routes into the admin dashboard sidebar by exporting an object of type `RouteConfig` import from `@medusajs/admin` in the same route file.
|
||||
|
||||
The object has one property `link`, which is an object having the following properties:
|
||||
|
||||
- `label`: a string indicating the sidebar item’s label of your custom route.
|
||||
- `icon`: an optional React component that acts as an icon in the sidebar. If none provided, a default icon is used.
|
||||
|
||||
For example, you can change the content of the previous route you created to export a config object:
|
||||
|
||||
```tsx title="src/admin/routes/custom/page.tsx"
|
||||
import { RouteConfig } from "@medusajs/admin"
|
||||
import { CustomIcon } from "../../icons/custom"
|
||||
|
||||
const CustomPage = () => {
|
||||
return (
|
||||
<div>
|
||||
This is my custom route
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const config: RouteConfig = {
|
||||
link: {
|
||||
label: "Custom Route",
|
||||
icon: CustomIcon,
|
||||
},
|
||||
}
|
||||
|
||||
export default CustomPage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Path Parameters
|
||||
|
||||
As mentioned earlier, you can create dynamic routes like `/a/custom/[id]` by creating a route file at the path `src/admin/routes/custom/[id]/page.tsx`.
|
||||
|
||||
To retrieve the path parameter, you can use the [useParams hook](https://reactrouter.com/en/main/hooks/use-params) retrieved from the [react-router-dom](https://reactrouter.com/en/main) package.
|
||||
|
||||
:::note
|
||||
|
||||
`react-router-dom` is available as one of the `@medusajs/admin` dependencies. You can also install it within your project using the following command:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install react-router-dom
|
||||
```
|
||||
|
||||
If you're installing it in a plugin with admin customizations, make sure to include it in `peerDependencies`.
|
||||
|
||||
:::
|
||||
|
||||
For example:
|
||||
|
||||
```tsx title="src/admin/routes/custom/[id]/page.tsx"
|
||||
import { useParams } from "react-router-dom"
|
||||
|
||||
const CustomPage = () => {
|
||||
const { id } = useParams()
|
||||
|
||||
return (
|
||||
<div>
|
||||
Passed ID: {id}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CustomPage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Routing Functionalities
|
||||
|
||||
If you want to use routing functionalities such as linking to another page or navigating between pages, you can use `react-router-dom`'s utility hooks and functions.
|
||||
|
||||
For example, to add a link to another page:
|
||||
|
||||
```tsx title="src/admin/routes/custom/page.tsx"
|
||||
import { Link } from "react-router-dom"
|
||||
|
||||
const CustomPage = () => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Link to={"/a/products"}>
|
||||
View Products
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CustomPage
|
||||
```
|
||||
|
||||
View [react-router-dom’s documentation](https://reactrouter.com/en/main) for other available components and hooks.
|
||||
|
||||
---
|
||||
|
||||
## Styling Route
|
||||
|
||||
Admin UI routes support [Tailwind CSS](https://tailwindcss.com/) by default.
|
||||
|
||||
For example, to customize your custom route:
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```tsx title="src/admin/routes/custom/page.tsx"
|
||||
const CustomPage = () => {
|
||||
return (
|
||||
<div
|
||||
className="bg-white p-8 border border-gray-200 rounded-lg">
|
||||
This is my custom route
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CustomPage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Querying and Mutating Data
|
||||
|
||||
You might need to interact with the Medusa backend from your admin route. To do so, you can utilize the [Medusa React package](../medusa-react/overview.mdx). It contains a collection of queries and mutation built on `@tanstack/react-query@4.22` that lets you interact with the Medusa backend.
|
||||
|
||||
:::note
|
||||
|
||||
Make sure to also install the Medusa React package first if you’re intending to use it, as explained in the [Medusa React guide](../medusa-react/overview.mdx).
|
||||
|
||||
:::
|
||||
|
||||
For example, you can retrieve available products and display them in your route:
|
||||
|
||||
```tsx title="src/admin/routes/custom/page.tsx"
|
||||
import { useAdminProducts } from "medusa-react"
|
||||
|
||||
const CustomPage = () => {
|
||||
const { products } = useAdminProducts()
|
||||
return (
|
||||
<div className="bg-white">
|
||||
{products?.map((product) => product.title)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CustomPage
|
||||
```
|
||||
|
||||
### Custom API Routes
|
||||
|
||||
You can also use `medusa-react` to interact with custom API Routes using [Custom Hooks utility functions](../medusa-react/overview.mdx#custom-hooks).
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Admin widgets](./widgets.md)
|
||||
- [Create a plugin for your admin customizations](../development/plugins/create.mdx)
|
||||
@@ -1,349 +0,0 @@
|
||||
---
|
||||
title: 'How to Create an Admin Setting Page'
|
||||
description: 'Learn how to create a new setting page in the admin dashboard.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
In this document, you’ll learn how to create a setting page in the admin.
|
||||
|
||||
## Overview
|
||||
|
||||
The [admin UI routes](./routes.md) allow you to add new pages to the admin dashboard. However, they can’t be used to add a new tab under the Setting page.
|
||||
|
||||
To do that, you need to create an Admin setting page. The page will automatically be shown as a tab under the Setting page in the admin. The tab leads to the content of your custom page.
|
||||
|
||||
A setting page is essentially a React Component created under the `src/admin/settings` directory.
|
||||
|
||||
This guide explains how to create a new setting page in the admin dashboard with some examples.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
It’s assumed you already have a Medusa backend with the admin plugin installed before you move forward with this guide. If not, you can follow [this documentation page](../create-medusa-app.mdx) to install a Medusa project.
|
||||
|
||||
### (Optional) TypeScript Preparations
|
||||
|
||||
Since setting pages are React components, they should be written in `.tsx` or `.jsx` files. If you’re using Typescript, you need to make some adjustments to avoid Typescript errors in your Admin files.
|
||||
|
||||
This section provides recommended configurations to avoid any TypeScript errors.
|
||||
|
||||
:::note
|
||||
|
||||
These changes may already be available in your Medusa project. They're included here for reference purposes.
|
||||
|
||||
:::
|
||||
|
||||
First, update your `tsconfig.json` with the following configurations:
|
||||
|
||||
```json title="tsconfig.json"
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2019",
|
||||
"module": "commonjs",
|
||||
"allowJs": true,
|
||||
"checkJs": false,
|
||||
"jsx": "react-jsx",
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"noEmit": false,
|
||||
"strict": false,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/"],
|
||||
"exclude": [
|
||||
"dist",
|
||||
"build",
|
||||
".cache",
|
||||
"tests",
|
||||
"**/*.spec.js",
|
||||
"**/*.spec.ts",
|
||||
"node_modules",
|
||||
".eslintrc.js"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The important changes to note here are the inclusion of the field `"jsx": "react-jsx"` and the addition of `"build"` and `“.cache”` to `exclude`.
|
||||
|
||||
The addition of `"jsx": "react-jsx"` specified how should TypeScript transform JSX, and excluding `build` and `.cache` ensures that TypeScript ignores build and development files.
|
||||
|
||||
Next, create the file `tsconfig.server.json` with the following content:
|
||||
|
||||
```json title="tsconfig.server.json"
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
/* Emit a single file with source maps instead of having a separate file. */
|
||||
"inlineSourceMap": true
|
||||
},
|
||||
"exclude": ["src/admin", "**/*.spec.js"]
|
||||
}
|
||||
```
|
||||
|
||||
This is the configuration that will be used to transpile your custom backend code, such as services or entities. The important part is that it excludes `src/admin` as that is where your Admin code will live.
|
||||
|
||||
Finally, create the file `tsconfig.admin.json` with the following content:
|
||||
|
||||
```json title="tsconfig.admin.json"
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "esnext"
|
||||
},
|
||||
"include": ["src/admin"],
|
||||
"exclude": ["**/*.spec.js"]
|
||||
}
|
||||
```
|
||||
|
||||
This is the configuration that will be used when transpiling your admin code.
|
||||
|
||||
---
|
||||
|
||||
## Create the Setting Page
|
||||
|
||||
In this section, you’ll learn the basics of creating an admin UI route.
|
||||
|
||||
### Step 1: Create File
|
||||
|
||||
Custom admin setting pages are added under the `src/admin/settings` directory of your Medusa project. The path of the file depends on the path you want the setting page to be available under. It is based on [Next.js 13’s App Router](https://nextjs.org/docs/app/building-your-application/routing/defining-routes).
|
||||
|
||||
All setting page paths are prefixed with `/a/settings`.
|
||||
|
||||
For example, if you want the setting page to be available in the admin dashboard under the path `/a/settings/custom` you should create your setting page under the file path `src/admin/settings/custom/page.tsx`.
|
||||
|
||||
:::note
|
||||
|
||||
Only one-level deep files are accepted. So, for example, you can create the page `src/admin/settings/custom/page.tsx`, but not `src/admin/settings/custom/nested/page.tsx`.
|
||||
|
||||
:::
|
||||
|
||||
### Step 2: Create React Component in File
|
||||
|
||||
For a setting page to be valid, it must default export a React component. There are no restrictions on the content of the React component. It must also export a configuration object that indicates how the tab is shown on the Setting page.
|
||||
|
||||
For example, you can create the file `src/admin/settings/custom/page.tsx` with the following content:
|
||||
|
||||
```tsx title="src/admin/settings/custom/page.tsx"
|
||||
import type { SettingConfig } from "@medusajs/admin"
|
||||
import { CustomIcon } from "../../icons/custom"
|
||||
|
||||
const CustomSettingPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>Custom Setting Page</h1>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const config: SettingConfig = {
|
||||
card: {
|
||||
label: "Custom",
|
||||
description: "Manage your custom settings",
|
||||
// optional
|
||||
icon: CustomIcon,
|
||||
},
|
||||
}
|
||||
|
||||
export default CustomSettingPage
|
||||
```
|
||||
|
||||
This creates a setting page that will be available at `/a/settings/custom`. The content of the page is defined in the exported React component.
|
||||
|
||||
The exported configuration also indicates the details of the tab that will be added to the Setting page. You must specify a label and description, and you can optionally specify an icon. The icon is a React component.
|
||||
|
||||
### Step 3: Test it Out
|
||||
|
||||
To test your admin setting page, run the following command in the root directory of the Medusa backend project:
|
||||
|
||||
```bash
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
This will build your admin and opens a window in your default browser to `localhost:7001`. After you log in, go to `localhost:7001/a/settings`. You’ll find a new tab available under a new Extensions section.
|
||||
|
||||
If you click on the tab, a new page will open with the content as defined in your React component.
|
||||
|
||||
---
|
||||
|
||||
## Setting Page Props
|
||||
|
||||
Every route receives props of the type `RouteProps`, which includes the `notify` prop. The `notify` prop is an object that includes the following attributes:
|
||||
|
||||
- `success`: a function that can be used to show a success message.
|
||||
- `error`: a function that can be used to show an error message.
|
||||
- `warn`: a function that can be used to show a warning message.
|
||||
- `info`: a function that can be used to show an info message.
|
||||
|
||||
For example:
|
||||
|
||||
```tsx title="src/admin/settings/custom/page.tsx"
|
||||
import type { SettingConfig } from "@medusajs/admin"
|
||||
import type { SettingProps } from "@medusajs/admin"
|
||||
|
||||
const CustomSettingPage = ({
|
||||
notify,
|
||||
}: SettingProps) => {
|
||||
|
||||
const handleClick = () => {
|
||||
notify.success("Success", "You clicked the button")
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Custom Setting Page</h1>
|
||||
<button onClick={handleClick}>
|
||||
Click Me
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const config: SettingConfig = {
|
||||
card: {
|
||||
label: "Custom",
|
||||
description: "Manage your custom settings",
|
||||
},
|
||||
}
|
||||
|
||||
export default CustomSettingPage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Styling Setting Page
|
||||
|
||||
Admin setting pages support [Tailwind CSS](https://tailwindcss.com/) by default.
|
||||
|
||||
For example, to customize the style of your custom setting page:
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```tsx title="src/admin/settings/custom/page.tsx"
|
||||
import type { SettingConfig } from "@medusajs/admin"
|
||||
|
||||
const CustomSettingPage = () => {
|
||||
return (
|
||||
<div
|
||||
className="bg-white p-8 border border-gray-200 rounded-lg"
|
||||
>
|
||||
<h1>Custom Setting Page</h1>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const config: SettingConfig = {
|
||||
card: {
|
||||
label: "Custom",
|
||||
description: "Manage your custom settings",
|
||||
},
|
||||
}
|
||||
|
||||
export default CustomSettingPage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use Routing Functionalities
|
||||
|
||||
If you want to use routing functionalities such as linking to another page or navigating between pages, you can use `react-router-dom`'s utility hooks and functions.
|
||||
|
||||
:::note
|
||||
|
||||
💡 `react-router-dom` is available as one of the `@medusajs/admin` dependencies. You can also install it within your project using the following command:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install react-router-dom
|
||||
```
|
||||
|
||||
If you're installing it in a plugin with admin customizations, make sure to include it in `peerDependencies`.
|
||||
|
||||
:::
|
||||
|
||||
For example, to add a link to another page:
|
||||
|
||||
```tsx title="src/admin/settings/custom/page.tsx"
|
||||
import type { SettingConfig } from "@medusajs/admin"
|
||||
import { Link } from "react-router-dom"
|
||||
|
||||
const CustomSettingPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>Custom Setting Page</h1>
|
||||
<Link to={"/a/products"}>
|
||||
View Products
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const config: SettingConfig = {
|
||||
card: {
|
||||
label: "Custom",
|
||||
description: "Manage your custom settings",
|
||||
},
|
||||
}
|
||||
|
||||
export default CustomSettingPage
|
||||
```
|
||||
|
||||
View [react-router-dom’s documentation](https://reactrouter.com/en/main) for other available components and hooks.
|
||||
|
||||
---
|
||||
|
||||
## Querying and Mutating Data
|
||||
|
||||
You might need to interact with the Medusa backend from your admin setting page. To do so, you can utilize the Medusa React package. It contains a collection of queries and mutations built on `@tanstack/react-query@4.22` that lets you interact with the Medusa backend.
|
||||
|
||||
:::note
|
||||
|
||||
Make sure to also install the Medusa React package first if you’re intending to use it, as explained in the Medusa React guide.
|
||||
|
||||
:::
|
||||
|
||||
For example, you can retrieve available products and display them in your route:
|
||||
|
||||
```tsx title="src/admin/settings/custom/page.tsx"
|
||||
import type { SettingConfig } from "@medusajs/admin"
|
||||
import { useAdminProducts } from "medusa-react"
|
||||
|
||||
const CustomSettingPage = () => {
|
||||
const { products } = useAdminProducts()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Custom Setting Page</h1>
|
||||
<div className="bg-white">
|
||||
{products?.map((product) => product.title)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const config: SettingConfig = {
|
||||
card: {
|
||||
label: "Custom",
|
||||
description: "Manage your custom settings",
|
||||
},
|
||||
}
|
||||
|
||||
export default CustomSettingPage
|
||||
```
|
||||
|
||||
### Custom API Routes
|
||||
|
||||
You can also use `medusa-react` to interact with custom API Routes using [Custom Hooks utility functions](../medusa-react/overview.mdx#custom-hooks).
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Admin widgets](./widgets.md)
|
||||
- [Create a plugin for your admin customizations](../development/plugins/create.mdx)
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,118 +0,0 @@
|
||||
---
|
||||
description: "In this document, you’ll learn about the current beta features in Medusa and how to enable them. This can include features that are hidden by a feature flag, or available by installing beta versions of some packages."
|
||||
---
|
||||
|
||||
# Beta Features
|
||||
|
||||
In this document, you’ll learn about the current beta features in Medusa and how to enable them. This can include features that are hidden by a feature flag, or available by installing `beta` versions of some packages.
|
||||
|
||||
## Product Categories
|
||||
|
||||
Organize products into categories of different hierarchies. You can have as many nested categories as necessary. You can also showcase the categories on your storefront.
|
||||
|
||||

|
||||
|
||||
Check out the [product categories](./modules/products/categories.md) documentation for more details on this feature.
|
||||
|
||||
### How to Enable Product Categories
|
||||
|
||||
To enable product categories you can either:
|
||||
|
||||
1. Set the `MEDUSA_FF_PRODUCT_CATEGORIES` environment variable to `true`.
|
||||
2. Or set the `product_categories` key in the Medusa backend's `featureFlags` setting to `true`.
|
||||
|
||||
You can learn more about enabling it in the [feature flags documentation](./development/feature-flags/toggle.md).
|
||||
|
||||
---
|
||||
|
||||
## Tax-Inclusive Pricing
|
||||
|
||||
Specify prices with taxes included. Medusa then takes care of calculating the tax amount applied in each region.
|
||||
|
||||

|
||||
|
||||
Check out the [tax-inclusive pricing](./modules/taxes/inclusive-pricing.md) documentation for more details on this feature.
|
||||
|
||||
### How to Enable Tax-Inclusive Pricing
|
||||
|
||||
To enable tax-inclusive pricing you can either:
|
||||
|
||||
1. Set the `MEDUSA_FF_TAX_INCLUSIVE_PRICING` environment variable to `true`.
|
||||
2. Or set the `tax_inclusive_pricing` key in the Medusa backend's `featureFlags` setting to `true`.
|
||||
|
||||
You can learn more about enabling it in the [feature flags documentation](./development/feature-flags/toggle.md).
|
||||
|
||||
---
|
||||
|
||||
## Order Editing
|
||||
|
||||
:::tip
|
||||
|
||||
This feature is enabled by default.
|
||||
|
||||
:::
|
||||
|
||||
Edit orders in your store by adding, updating, or deleting items in the order. You can also require customer confirmation of the order edit and process payment, such as authorizing additional payment or refunding the difference from the original payment.
|
||||
|
||||

|
||||
|
||||
Check out the [order edits](./modules/orders/orders.md#order-edits) documentation for more details on this feature.
|
||||
|
||||
### How to Enable Order Editing
|
||||
|
||||
To enable order editing you can either:
|
||||
|
||||
1. Set the `MEDUSA_FF_ORDER_EDITING` environment variable to `true`.
|
||||
2. Or set the `order_editing` key in the Medusa backend's `featureFlags` setting to `true`.
|
||||
|
||||
You can learn more about enabling it in the [feature flags documentation](./development/feature-flags/toggle.md).
|
||||
|
||||
---
|
||||
|
||||
## Sales Channels
|
||||
|
||||
:::tip
|
||||
|
||||
This feature is enabled by default.
|
||||
|
||||
:::
|
||||
|
||||
Support an omnichannel experience by specifying product availability in various sales channels.
|
||||
|
||||

|
||||
|
||||
Check out the [sales channels](./modules/sales-channels/overview.mdx) documentation for more details on this feature.
|
||||
|
||||
### How to Enable Sales Channels
|
||||
|
||||
To enable sales channels you can either:
|
||||
|
||||
1. Set the `MEDUSA_FF_SALES_CHANNELS` environment variable to `true`.
|
||||
2. Or set the `sales_channels` key in the Medusa backend's `featureFlags` setting to `true`.
|
||||
|
||||
You can learn more about enabling it in the [feature flags documentation](./development/feature-flags/toggle.md).
|
||||
|
||||
---
|
||||
|
||||
## Publishable API Keys
|
||||
|
||||
:::tip
|
||||
|
||||
This feature is enabled by default.
|
||||
|
||||
:::
|
||||
|
||||
Create API keys that can be used by storefront clients, such as websites or mobile apps, to specify the context and resources associated with your request. Publishable API keys promote an omnichannel experience managed through the admin.
|
||||
|
||||

|
||||
|
||||
Check out the [publishable API keys](./development/publishable-api-keys/index.mdx) documentation for more details on this feature.
|
||||
|
||||
### How to Enable Publishable API Keys
|
||||
|
||||
To enable publishable API keys you can either:
|
||||
|
||||
1. Set the `MEDUSA_FF_PUBLISHABLE_API_KEYS` environment variable to `true`.
|
||||
2. Or set the `publishable_api_keys` key in the Medusa backend's `featureFlags` setting to `true`.
|
||||
|
||||
You can learn more about enabling it in the [feature flags documentation](./development/feature-flags/toggle.md).
|
||||
@@ -1,184 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to install the Medusa CLI Tool. Medusa CLI Tool can be used to perform actions such as create a new Medusa backend, run migrations, create a new admin user, and more.'
|
||||
---
|
||||
|
||||
import DetailsListSection from "../troubleshooting/cli-installation-errors/_reusable-section.mdx"
|
||||
|
||||
# CLI Reference
|
||||
|
||||
This document serves as a reference to the Medusa CLI tool including how to install it and what commands are available.
|
||||
|
||||
## Overview
|
||||
|
||||
The Medusa CLI serves as a tool that allows you to perform important commands while developing with Medusa.
|
||||
|
||||
To use Medusa, it is required to install the CLI tool as it is used to create a new Medusa backend.
|
||||
|
||||
---
|
||||
|
||||
## How to Install CLI Tool
|
||||
|
||||
In your Medusa backend's directory, you can use the Medusa CLI tool using NPX. For example:
|
||||
|
||||
```bash
|
||||
npx medusa --help
|
||||
```
|
||||
|
||||
Alternatively, you can install the CLI tool globally:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install @medusajs/medusa-cli -g
|
||||
medusa --help
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting Installation
|
||||
|
||||
<DetailsListSection />
|
||||
|
||||
---
|
||||
|
||||
## Commands
|
||||
|
||||
### new
|
||||
|
||||
Create a new Medusa backend.
|
||||
|
||||
```bash
|
||||
medusa new [<backend_name> [<starter_url>]]
|
||||
```
|
||||
|
||||
#### Arguments
|
||||
|
||||
| Name | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| `backend_name` | The name of the Medusa backend. It will be used as the name of the directory created. | If not provided, you’ll be prompted to enter it. |
|
||||
| `starter_url` | The URL of the starter to create the backend from. | The default starter is used. |
|
||||
|
||||
#### Options
|
||||
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| `-y`, `--useDefaults` | If the flag is set the command will not interactively collect database credentials. |
|
||||
| `--skip-db` | If the flag is set the command will not attempt to complete the database setup. |
|
||||
| `--skip-migrations` | If the flag is set the command will not attempt to complete the database migration. |
|
||||
| `--skip-env` | If the flag is set the command will not attempt to populate .env. |
|
||||
| `--db-user <user>` | The database user to use for database setup and migrations. |
|
||||
| `--db-database <database>` | The database used for database setup and migrations. |
|
||||
| `--db-pass <password>` | The database password to use for database setup and migrations. |
|
||||
| `--db-port <port>` | The database port to use for database setup and migrations. |
|
||||
| `--db-host <host>` | The database host to use for database setup and migrations. |
|
||||
|
||||
### develop
|
||||
|
||||
Start development backend. This command watches files for any changes to rebuild the files and restart the backend.
|
||||
|
||||
```bash
|
||||
medusa develop
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| `-H <host>`, `--host <host>` | Set host. Defaults to `localhost`. |
|
||||
| `-p <port>`, `--port <port>` | Set port. Defaults to `9000`. |
|
||||
|
||||
### start
|
||||
|
||||
Start backend in production.
|
||||
|
||||
```bash
|
||||
medusa start
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| `-H <host>`, `--host <host>` | Set host. Defaults to `localhost`. |
|
||||
| `-p <port>`, `--port <port>` | Set port. Defaults to `9000`. |
|
||||
|
||||
### migrations
|
||||
|
||||
Perform migration actions to reflect changes on the database, such as running or reverting migrations.
|
||||
|
||||
```bash
|
||||
medusa migrations <action>
|
||||
```
|
||||
|
||||
#### Arguments
|
||||
|
||||
| Name | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| `action` | The action to perform. Values can be `run`, `show`, or `revert`. `run` is used to run the migrations; `show` is used to only show what migrations are available to run; and `revert` is to undo the last migration. | This argument is required and does not have a default value. |
|
||||
|
||||
### seed
|
||||
|
||||
Migrates and populates the database with the provided file.
|
||||
|
||||
```bash
|
||||
medusa seed --seed-file=<file_path>
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| `-f=<file_path>`, `--seed-file=<file_path>` | Path to the file where the seed is defined. (required) |
|
||||
| `-m`, `--migrate` | Flag to indicate if migrations should be run prior to seeding the database. Default is true. |
|
||||
|
||||
### user
|
||||
|
||||
Creates a new admin user.
|
||||
|
||||
```bash
|
||||
medusa user --email <email> [--password <password>]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| `-e <email>`, `--email <email>` | The email to create a user with. (required) |
|
||||
| `-p <password>`, `--password <password>` | The password to use with the user. If not included, the user will not have a password. |
|
||||
| `-i <id>`, `--id <id>` | The user’s ID. By default it is automatically generated. |
|
||||
| `--invite` | Whether to create an invite instead of a user. When using this option, you don't need to specify a password. If ran successfully, you'll receive the invite token in the output. |
|
||||
|
||||
### start-cluster
|
||||
|
||||
Starts the Medusa backend in [cluster mode](https://expressjs.com/en/advanced/best-practice-performance.html#run-your-app-in-a-cluster). Running in cluster mode will likely significantly improve performance as the workload and tasks are distributed among all available instances instead of a single one.
|
||||
|
||||
```bash
|
||||
medusa start-cluster
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| `-c <number>`, `--cpus <number>` | The number of CPUs that Medusa can consume. By default, Medusa will try to consume all CPUs in cluster mode if this option isn't specified. |
|
||||
| `-H <host>`, `--host <host>` | Set host. Defaults to `localhost`. |
|
||||
| `-p <port>`, `--port <port>` | Set port. Defaults to `9000`. |
|
||||
|
||||
### telemetry
|
||||
|
||||
Enable or disable the collection of anonymous data usage. If no option is provided, the command will enable the collection of anonymous data usage.
|
||||
|
||||
```bash
|
||||
medusa telemetry
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| `--enable` | Enable telemetry (default) |
|
||||
| `--disable` | Disable telemetry |
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Configure Medusa](../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx)
|
||||
- [Set up your development environment](../development/backend/prepare-environment.mdx)
|
||||
@@ -1,56 +0,0 @@
|
||||
---
|
||||
sidebar_label: "Admin Translation"
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Contribute by Translating Admin
|
||||
|
||||
In this document, you'll learn how you can contribute to Medusa by translating the Medusa Admin.
|
||||
|
||||
## Overview
|
||||
|
||||
The Medusa Admin supports multiple languages, with the default being English. To ensure a wide support for different languages, your contribution by translating to other languages you're fluent in is highly appreciated.
|
||||
|
||||
This type of contribution is a no-code contribution, meaning you don't need advanced technical skills to contribute.
|
||||
|
||||
---
|
||||
|
||||
## How to Contribute Translation
|
||||
|
||||
1\. Clone the [Medusa monorepository](https://github.com/medusajs/medusa) to your local machine:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/medusajs/medusa.git
|
||||
```
|
||||
|
||||
If you already have it cloned, make sure to pull the latest changes from the `develop` branch.
|
||||
|
||||
2\. Create a branch that will be used to open the pull request later:
|
||||
|
||||
```bash
|
||||
git checkout -b feat/translate-<LANGUAGE>
|
||||
```
|
||||
|
||||
Where `<LANGUAGE>` is your language name. For example, `feat/translate-da`.
|
||||
|
||||
3\. Create a new directory under `packages/admin-ui/ui/public/locales` with its name being the [ISO 2 character code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) of your language. For example, `da`. In the new directory, create the file `translation.json`.
|
||||
|
||||
4\. Copy the content of the English translation file located at `packages/admin-ui/ui/public/locales/en/translation.json` and paste it in your new `translation.json` file.
|
||||
|
||||
5\. In the file, leave the key names as-is, and only translate the values.
|
||||
|
||||
6\. Next, you need to add the new language into the multi-language configurations. To do that, go to the file `packages/admin-ui/ui/src/i18n/index.ts`.
|
||||
|
||||
7\. In `packages/admin-ui/ui/src/i18n/index.ts`, add the new language to the `supportedLanguages` array as an object. The object accepts two properties: `locale` for the ISO 2 character code, and `name` for the name of the language. The name of the language should be the translated name, not the English name. For example:
|
||||
|
||||
```ts title="packages/admin-ui/ui/src/i18n/index.ts"
|
||||
export const supportedLanguages = [
|
||||
// other languages...
|
||||
{
|
||||
locale: "da",
|
||||
name: "Dansk",
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
8\. Once you're done, push the changes into your branch and open a pull request on GitHub.
|
||||
@@ -1,594 +0,0 @@
|
||||
---
|
||||
sidebar_label: "Docs"
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Docs Contribution Guidelines
|
||||
|
||||
Thank you for your interest in contributing to the documentation! You will be helping the open source community and other developers interested in learning more about Medusa and using it.
|
||||
|
||||
:::tip
|
||||
|
||||
This guide is specific to contributing to the documentation. If you’re interested in contributing to Medusa’s codebase, check out the [contributing guidelines in the Medusa GitHub repository](https://github.com/medusajs/medusa/blob/develop/CONTRIBUTING.md).
|
||||
|
||||
:::
|
||||
|
||||
## Documentation Workspace
|
||||
|
||||
Medusa's documentation projects are all part of the documentation yarn workspace, which you can find in the [medusa repository](https://github.com/medusajs/medusa) under the `www` directory.
|
||||
|
||||
The workspace has the following two directories:
|
||||
|
||||
- `apps`: this directory holds the different documentation websites and projects.
|
||||
- `docs`: includes the codebase for the main documentation website (the one you're viewing this documentation on). It's built with [Docusaurus](https://docusaurus.io/).
|
||||
- `api-reference`: includes the codebase for the API reference website. It's built with [Next.js 13](https://nextjs.org/).
|
||||
- `ui`: includes the codebase for the Medusa UI documentation website. It's built with [Next.js 13](https://nextjs.org/).
|
||||
- `packages`: this directory holds the shared packages and components necessary for the development of the projects in the `apps` directory.
|
||||
- `docs-ui` includes the shared React components between the different apps.
|
||||
- `eslint-config-docs` includes the shared ESLint configuration between the different apps and packages.
|
||||
- `tailwind` includes the shared Tailwind CSS configuration between the different apps and packages.
|
||||
- `tsconfig` includes the shared TypeScript configuration between the different apps and packages.
|
||||
|
||||
---
|
||||
|
||||
## Documentation Content
|
||||
|
||||
### Main Documentation Website
|
||||
|
||||
The documentation content is written in Markdown format and is located in the [www/apps/docs/content](https://github.com/medusajs/medusa/tree/develop/www/apps/docs/content) directory of the Medusa repository. If you’re not familiar with Markdown, check out [this cheat sheet](https://www.markdownguide.org/cheat-sheet/) for a quick start.
|
||||
|
||||
You’ll also find MDX files. MDX files combine the power of Markdown with React. So, the content of the file can contain JSX components and import statements, among other features. You can learn more about [MDX in docusaurus’s guide.](https://docusaurus.io/docs/markdown-features/react).
|
||||
|
||||
:::note
|
||||
|
||||
Documentation pages under the `www/apps/docs/content/references` directory are generated automatically from the source code under the `packages/medusa` directory. So, you can't directly make changes to them. Instead, you'll have to make changes to the comments in the original source code.
|
||||
|
||||
:::
|
||||
|
||||
### API Reference
|
||||
|
||||
The API reference's content is split into two types:
|
||||
|
||||
1. Static content, which are the content related to getting started, expanding fields, and more. These are located in the [www/apps/api-reference/app/_mdx](https://github.com/medusajs/medusa/tree/develop/www/apps/api-reference/app/_mdx) directory. They are MDX files.
|
||||
2. OpenAPI specs that are shown to developers when checking the reference of an API Route. These are automatically generated from comments on API Routes. So, if you find issues in them or want to make improvements, you have to find the API Routes under the [`packages/medusa/src/api`](https://github.com/medusajs/medusa/tree/develop/packages/medusa/src/api) directory and make changes to its comments.
|
||||
|
||||
### Medusa UI Documentation
|
||||
|
||||
The content of the Medusa UI documentation are located under the [www/apps/ui/src/content/docs](https://github.com/medusajs/medusa/tree/develop/www/apps/ui/src/content/docs) directory. They are MDX files.
|
||||
|
||||
The UI documentation also shows code examples, which you can find under the [www/apps/ui/src/examples](https://github.com/medusajs/medusa/tree/develop/www/apps/ui/src/examples) directory.
|
||||
|
||||
---
|
||||
|
||||
## Style Guide
|
||||
|
||||
When you contribute to the documentation content, make sure to follow the [documentation style guide](https://www.notion.so/Style-Guide-Docs-fad86dd1c5f84b48b145e959f36628e0).
|
||||
|
||||
---
|
||||
|
||||
## How to Contribute
|
||||
|
||||
If you’re fixing errors in an existing documentation page, you can scroll down to the end of the page and click on the “Edit this page” link. You’ll be redirected to the GitHub edit form of that page and you can make edits directly and submit a pull request (PR).
|
||||
|
||||
If you’re adding a new page or contributing to the codebase, you need to fork the repository, create a new branch, and make all changes necessary in your repository. Then, once you’re done, create a PR in the Medusa repository.
|
||||
|
||||
### Base Branch
|
||||
|
||||
When you make an edit to an existing documentation page or fork the repository to make changes to the documentation, you have to create a new branch.
|
||||
|
||||
Documentation contributions always use `develop` as the base branch. Make sure to also open your PR against the `develop` branch.
|
||||
|
||||
### Branch Name
|
||||
|
||||
Make sure that the branch name starts with `docs/`. For example, `docs/fix-services`. Vercel deployed previews are only triggered for branches starting with `docs/`.
|
||||
|
||||
### Pull Request Conventions
|
||||
|
||||
When you create a pull request, prefix the title with `docs:` or `docs(PROJECT_NAME):`, where `PROJECT_NAME` is the name of the documentation project this pull request pertains to. For example, `docs(ui): fix titles`.
|
||||
|
||||
<!-- vale off -->
|
||||
|
||||
In the body of the PR, explain clearly what the PR does. If the PR solves an issue, use [closing keywords](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) with the issue number. For example, “Closes #1333”.
|
||||
|
||||
<!-- vale on -->
|
||||
|
||||
---
|
||||
|
||||
## Main Documentation Sidebar
|
||||
|
||||
When you add a new page to the documentation, you must add the new page in `www/apps/docs/sidebars.js`. In this file, an object is exported. This object holds more than one sidebar. The properties of the object indicate the internal sidebar name, and the value is an array of sidebar items in that sidebar.
|
||||
|
||||
You can learn more about the syntax used [here](https://docusaurus.io/docs/sidebar/items).
|
||||
|
||||
### Terminology
|
||||
|
||||
When the documentation page is a conceptual or an overview documentation, the label in the sidebar should start with a noun.
|
||||
|
||||
When the documentation page is tutorial documentation, the label in the sidebar should start with a verb. Exceptions to this rule are integration documentation and upgrade guides.
|
||||
|
||||
### Sidebar Prefixes
|
||||
|
||||
How-to guides in the sidebar for documentation pages under the Commerce Modules section are typically prefixed with one of the following terms:
|
||||
|
||||
- `Backend: `: Used when the how-to guide explains how to do something on the Medusa backend.
|
||||
- `Admin: `: Used when the how-to guide explains how to do something using the admin APIs.
|
||||
- `Store: `: Used when the how-to guide explains how to do something using the store APIs.
|
||||
|
||||
### Sidebar Icon
|
||||
|
||||
To add an icon to the sidebar item, start by checking if the icon is already exported in the file `www/apps/docs/src/theme/Icon`. If not, you can either export the icon from the [@medusajs/icons](https://docs.medusajs.com/ui/icons/overview), or add the new icon as a React component in the `www/apps/docs/src/theme/Icon/Icon<Name>/index.tsx` file, where `<Name>` is the camel-case name of your icon. The icon must be added to the React component as an SVG element.
|
||||
|
||||
For example:
|
||||
|
||||
```tsx title="www/docs/src/theme/Icon/Bolt/index.tsx"
|
||||
import React from "react"
|
||||
import { IconProps } from "@medusajs/icons/dist/types"
|
||||
|
||||
export default function IconBolt(props: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width={props.width || 20}
|
||||
height={props.height || 20}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none" xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M3.125..."
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
stroke="currentColor" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Make sure to set the `stroke` or `fill` of the icon to `currentColor` as shown in the example above. The source code for the Sidebar passes the icon a color. So, this ensures the color is correctly used.
|
||||
|
||||
If you added a new icon, add it in the exported object in the file `www/apps/docs/src/theme/Icon/index.ts`, where the property is the kebab-case version of the icon's name, and the value being the component you created. Make sure to add it in the correct alphabetical position as well. For example:
|
||||
|
||||
```ts title="www/docs/src/theme/Icon/index.ts"
|
||||
import IconBolt from "./Bolt"
|
||||
import IconBoltSolid from "./BoltSolid"
|
||||
// other imports
|
||||
|
||||
export default {
|
||||
// other icons
|
||||
"bolt": IconBolt,
|
||||
"bolt-solid": IconBoltSolid,
|
||||
// other icons
|
||||
}
|
||||
```
|
||||
|
||||
Finally, you can add the icon to the sidebar item by adding a `sidebar_icon` property to the `customProps` property and setting its value to the kebab-cased version of the icon's name. For example:
|
||||
|
||||
```js title="www/docs/sidebars.js"
|
||||
module.exports = {
|
||||
// other sidebars
|
||||
homepage: [
|
||||
{
|
||||
// other properties
|
||||
customProps: {
|
||||
sidebar_icon: "book-open",
|
||||
},
|
||||
},
|
||||
// other items
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### Sidebar Item Types
|
||||
|
||||
There are different sidebar item types used in the documentation:
|
||||
|
||||
- Homepage Items: If a sidebar item is shown under the `homepage` sidebar, you should set the `className` property of the item to `homepage-sidebar-item`. You can use this with other sidebar item types. For example:
|
||||
|
||||
```js title="www/docs/sidebars.js"
|
||||
module.exports = {
|
||||
// other sidebars
|
||||
homepage: [
|
||||
{
|
||||
type: "doc",
|
||||
// other properties
|
||||
className: "homepage-sidebar-item",
|
||||
},
|
||||
// other items
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
- Sidebar Title: This item is used as a title to the sidebar, typically added at the top of the sidebar. You typically would also use an icon with it. To use this item, add a `sidebar_is_title` property to the `customProps` object of the item with its value being `true`. For example:
|
||||
|
||||
```js title="www/docs/sidebars.js"
|
||||
module.exports = {
|
||||
// other sidebars
|
||||
modules: [
|
||||
// other items
|
||||
{
|
||||
type: "doc",
|
||||
id: "modules/overview",
|
||||
label: "Commerce Modules",
|
||||
customProps: {
|
||||
sidebar_is_title: true,
|
||||
sidebar_icon: "puzzle",
|
||||
},
|
||||
},
|
||||
// other items
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
- Back Item: This item is used to show a back button, typically at the top of the sidebar. To use this item, add the `sidebar_is_back_link` property to the `customProps` object of the item, with its value set to true. Also, add the `sidebar_icon` property to the `customProps` object with its value set to `back-arrow`. For example:
|
||||
|
||||
```js title="www/docs/sidebars.js"
|
||||
module.exports = {
|
||||
// other sidebars
|
||||
core: [
|
||||
// other items
|
||||
{
|
||||
type: "ref",
|
||||
id: "homepage",
|
||||
label: "Back to home",
|
||||
customProps: {
|
||||
sidebar_is_back_link: true,
|
||||
sidebar_icon: "back-arrow",
|
||||
},
|
||||
},
|
||||
// other items
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
- Group Divider Item: This item is used if a sidebar item does not link to any document and is only used to separate between sidebar sections. The item must be of type `html`, and its `value` property holds the text that should be shown in the divider. You must also add in the `customProps` object of the item the property `sidebar_is_group_divider` with its value being `true`. For example:
|
||||
|
||||
```js title="www/docs/sidebars.js"
|
||||
module.exports = {
|
||||
// other sidebars
|
||||
homepage: [
|
||||
// other items
|
||||
{
|
||||
type: "html",
|
||||
value: "Browse Docs",
|
||||
customProps: {
|
||||
sidebar_is_group_divider: true,
|
||||
},
|
||||
className: "homepage-sidebar-item",
|
||||
},
|
||||
// other items
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
- Group Headline Item: This item is used if a sidebar item does not link to any document and is only used to indicate the beginning of a new section or group in the sidebar. To use this item, set the `type` of the item to `category`, and add the `sidebar_is_group_headline` property to the `customProps` object of the item, with its value set to `true`. For example:
|
||||
|
||||
```js title="www/docs/sidebars.js"
|
||||
module.exports = {
|
||||
// other sidebars
|
||||
modules: [
|
||||
// other items
|
||||
{
|
||||
type: "category",
|
||||
label: "Regions and Currencies",
|
||||
collapsible: false,
|
||||
customProps: {
|
||||
sidebar_is_group_headline: true,
|
||||
},
|
||||
items: [
|
||||
// items within group or section
|
||||
],
|
||||
},
|
||||
// other items
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
- Soon Item: This item is used to indicate that a certain guide will be added soon, but it does not actually link to any document. To use this item, set the `type` of the item to `link`, its `href` property to `#`, and add to the `customProps` object the property `sidebar_is_soon` with its value set to `true`. For example:
|
||||
|
||||
```js title="www/docs/sidebars.js"
|
||||
module.exports = {
|
||||
// other sidebars
|
||||
modules: [
|
||||
// other items
|
||||
{
|
||||
type: "link",
|
||||
href: "#",
|
||||
label: "Currencies",
|
||||
customProps: {
|
||||
sidebar_is_soon: true,
|
||||
},
|
||||
},
|
||||
// other items
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes and Additional Information
|
||||
|
||||
:::note
|
||||
|
||||
This only works in the main documentation website.
|
||||
|
||||
:::
|
||||
|
||||
When displaying notes and additional information on a documentation page, use [Admonitions](https://docusaurus.io/docs/markdown-features/admonitions). Make sure the type of admonition used matches the note’s importance to the current document.
|
||||
|
||||
If the note is something developers have to be careful of doing or not doing, use the `danger` admonition based on how critical it is.
|
||||
|
||||
If the note displays helpful information and tips that may not be in the scope of the documentation page, use the `tip` admonition.
|
||||
|
||||
For all other note types, use the `note` admonition.
|
||||
|
||||
---
|
||||
|
||||
## Images
|
||||
|
||||
If you are adding images to a documentation page, you can host the image on [Imgur](https://imgur.com) for free to include it in the PR. Our team will later upload it to our image hosting.
|
||||
|
||||
---
|
||||
|
||||
## Code Blocks
|
||||
|
||||
:::note
|
||||
|
||||
These sections only works in the main documentation website.
|
||||
|
||||
:::
|
||||
|
||||
### Use Tabs with Code Blocks
|
||||
|
||||
To use Tabs with Code Blocks, you have to use [Docusaurus's `Tabs` and `TabItem` components](https://docusaurus.io/docs/markdown-features/code-blocks#multi-language-support-code-blocks).
|
||||
|
||||
You must also pass to the `Tabs` component the prop `isCodeTabs={true}` to ensure correct styling.
|
||||
|
||||
For example:
|
||||
|
||||
~~~md
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.uploads.create(file) // file is an instance of File
|
||||
.then(({ uploads }) => {
|
||||
const key = uploads[0].key
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/uploads' \
|
||||
-H 'x-medusa-access-token: <API_TOKEN>' \
|
||||
-H 'Content-Type: text/csv' \
|
||||
-F 'files=@"<FILE_PATH_1>"'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
~~~
|
||||
|
||||
### Add Title to Code Block with Tabs
|
||||
|
||||
If you want to add a title to a code block with tabs, add the `codeTitle` prop to the `Tabs` component.
|
||||
|
||||
For example:
|
||||
|
||||
```md
|
||||
<Tabs
|
||||
groupId="request-type"
|
||||
isCodeTabs={true}
|
||||
codeTitle="/src/services/hello.ts">
|
||||
```
|
||||
|
||||
### Add Title to Code Block without Tabs
|
||||
|
||||
To add a title to a code block without tabs:
|
||||
|
||||
~~~md
|
||||
```js title="src/index.ts"
|
||||
console.log("hello")
|
||||
```
|
||||
~~~
|
||||
|
||||
### Remove Report Button
|
||||
|
||||
Some code block don't need a report button. To remove the report button, use the `noReport` metadata.
|
||||
|
||||
For example:
|
||||
|
||||
~~~md
|
||||
```bash noReport
|
||||
medusa new my-medusa-store
|
||||
```
|
||||
~~~
|
||||
|
||||
### Remove Copy Button
|
||||
|
||||
Some code blocks don't need a copy button. To remove the copy button, use the `noCopy` metadata:
|
||||
|
||||
For example:
|
||||
|
||||
~~~md
|
||||
```bash noCopy
|
||||
medusa new my-medusa-store
|
||||
```
|
||||
~~~
|
||||
|
||||
---
|
||||
|
||||
## NPM and Yarn Code Blocks
|
||||
|
||||
If you’re adding code blocks that use NPM and Yarn, you must use the [npm2yarn syntax](https://docusaurus.io/docs/markdown-features/code-blocks#npm2yarn-remark-plugin).
|
||||
|
||||
For example:
|
||||
|
||||
~~~md
|
||||
```bash npm2yarn
|
||||
npm run start
|
||||
```
|
||||
~~~
|
||||
|
||||
The code snippet must be written using NPM, and the `npm2yarn` plugin will automatically transform it to Yarn.
|
||||
|
||||
### Global Option
|
||||
|
||||
When a command uses the global option `-g`, add it at the end of the NPM command to ensure that it’s transformed to a Yarn command properly. For example:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install @medusajs/medusa-cli -g
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Linting with Vale
|
||||
|
||||
Medusa uses [Vale](https://vale.sh/) to lint documentation pages and perform checks on incoming PRs into the repository.
|
||||
|
||||
### Result of Vale PR Checks
|
||||
|
||||
You can check the result of running the "lint" action on your PR by clicking the Details link next to it. You can find there all errors that you need to fix.
|
||||
|
||||
### Run Vale Locally
|
||||
|
||||
If you want to check your work locally, you can do that by:
|
||||
|
||||
1. [Installing Vale](https://vale.sh/docs/vale-cli/installation/) on your machine.
|
||||
2. Changing to the `www/vale` directory:
|
||||
|
||||
```bash
|
||||
cd www/vale
|
||||
```
|
||||
|
||||
3\. Running the `run-vale` script:
|
||||
|
||||
```bash
|
||||
# to lint content for the main documentation
|
||||
./run-vale.sh docs content error references
|
||||
# to lint content for the API reference
|
||||
./run-vale.sh api-reference app/_mdx error
|
||||
# to lint content for the Medusa UI documentation
|
||||
./run-vale.sh ui src/content/docs error
|
||||
```
|
||||
|
||||
### VS Code Extension
|
||||
|
||||
To facilitate writing documentation, you can optionally use the [Vale VS Code extension](https://github.com/errata-ai/vale-vscode). This will show you any errors in your documentation while writing it.
|
||||
|
||||
### Linter Exceptions
|
||||
|
||||
If it's needed to break some style guide rules in a document, you can wrap the parts that the linter shouldn't scan with the following comments in the `md` or `mdx` files:
|
||||
|
||||
```md
|
||||
<!-- vale off -->
|
||||
|
||||
content that shouldn't be scanned for errors here...
|
||||
|
||||
<!-- vale on -->
|
||||
```
|
||||
|
||||
You can also disable specific rules. For example:
|
||||
|
||||
```md
|
||||
<!-- vale docs.Numbers = NO -->
|
||||
|
||||
Medusa supports Node versions 14 and 16.
|
||||
|
||||
<!-- vale docs.Numbers = YES -->
|
||||
```
|
||||
|
||||
If you use this in your PR, you must justify its usage.
|
||||
|
||||
---
|
||||
|
||||
## Linting with ESLint
|
||||
|
||||
Medusa uses ESlint to lint code blocks both in the content and the code base of the documentation apps.
|
||||
|
||||
### Linting Content with ESLint
|
||||
|
||||
Each PR runs through a check that lints the code in the content files using ESLint. The action's name is `content-eslint`.
|
||||
|
||||
If you want to check content ESLint errors locally and fix them, you can do that by:
|
||||
|
||||
1\. Install the dependencies in the `www` directory:
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
```
|
||||
|
||||
2\. Run the turbo command in the `www` directory:
|
||||
|
||||
```bash
|
||||
turbo run lint:content
|
||||
```
|
||||
|
||||
This will fix any fixable errors, and show errors that require your action.
|
||||
|
||||
### Linting Code with ESLint
|
||||
|
||||
Each PR runs through a check that lints the code in the content files using ESLint. The action's name is `code-docs-eslint`.
|
||||
|
||||
If you want to check code ESLint errors locally and fix them, you can do that by:
|
||||
|
||||
1\. Install the dependencies in the `www` directory:
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
```
|
||||
|
||||
2\. Run the turbo command in the `www` directory:
|
||||
|
||||
```bash
|
||||
turbo run lint
|
||||
```
|
||||
|
||||
This will fix any fixable errors, and show errors that require your action.
|
||||
|
||||
### ESLint Exceptions
|
||||
|
||||
:::note
|
||||
|
||||
These exceptions only work in the main documentation website.
|
||||
|
||||
:::
|
||||
|
||||
If some code blocks have errors that can't or shouldn't be fixed, you can add the following command before the code block:
|
||||
|
||||
~~~md
|
||||
<!-- eslint-skip -->
|
||||
|
||||
```js
|
||||
console.log("This block isn't linted")
|
||||
```
|
||||
|
||||
```js
|
||||
console.log("This block is linted")
|
||||
```
|
||||
~~~
|
||||
|
||||
You can also disable specific rules. For example:
|
||||
|
||||
~~~md
|
||||
<!-- eslint-disable semi -->
|
||||
|
||||
```js
|
||||
console.log("This block can use semicolons");
|
||||
```
|
||||
|
||||
```js
|
||||
console.log("This block can't use semi colons")
|
||||
```
|
||||
~~~
|
||||
|
||||
---
|
||||
|
||||
## Need Additional Help
|
||||
|
||||
If you need any additional help while contributing, you can join Medusa's [Discord server](https://discord.gg/medusajs) and ask Medusa’s core team as well as the community any questions.
|
||||
@@ -1,263 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to create a composable commerce platform using Medusa. This quickstart guide will help you set up your Medusa backend and the admin dashboard.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Feedback from '@site/src/components/Feedback';
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import Icons from '@theme/Icon';
|
||||
import DetailsList from '@site/src/components/DetailsList'
|
||||
import TypeErrorSection from "./troubleshooting/create-medusa-app-errors/_typeerror.md"
|
||||
import OtherErrorsSection from "./troubleshooting/create-medusa-app-errors/_other-errors.mdx"
|
||||
import EagainError from "./troubleshooting/create-medusa-app-errors/_eagain-error.md"
|
||||
import ConnectionErrorSection from './troubleshooting/database-errors/_connection-error.md'
|
||||
import FreshInstallationSection from './troubleshooting/awilix-resolution-error/_fresh-installation.md'
|
||||
import EaddrinuseSection from './troubleshooting/eaddrinuse.md'
|
||||
import InvalidTokenError from './troubleshooting/create-medusa-app-errors/_no-browser-token-error.md'
|
||||
import PostgresDockerError from './troubleshooting/database-errors/_docker.md'
|
||||
import DbUrlError from './troubleshooting/create-medusa-app-errors/_db-url-error.md'
|
||||
import ForwardingError from './troubleshooting/create-medusa-app-errors/_forwarding.md'
|
||||
|
||||
# Install Medusa with create-medusa-app
|
||||
|
||||
Medusa is a toolkit for developers to create digital commerce applications. In its simplest form, Medusa is a Node.js backend with the core API, plugins, and modules installed through npm.
|
||||
|
||||
`create-medusa-app` is a command that facilitates creating a Medusa ecosystem. It installs the Medusa backend and admin dashboard, along with the necessary configurations to run the backend.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before you can install and use Medusa, you need the following tools installed on your machine:
|
||||
|
||||
- [Node.js v16+](./development/backend/prepare-environment.mdx#nodejs)
|
||||
- [Git](./development/backend/prepare-environment.mdx#git)
|
||||
- [PostgreSQL](./development/backend/prepare-environment.mdx#postgresql). The PostgreSQL server should also be running during your installation process.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Run create-medusa-app
|
||||
|
||||
In your terminal, run the following command:
|
||||
|
||||
<Tabs groupId="npxyarn" isCodeTabs={true}>
|
||||
<TabItem value="npx" label="NPX" default>
|
||||
|
||||
```bash
|
||||
npx create-medusa-app@latest
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
{/* <TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn create medusa-app
|
||||
```
|
||||
|
||||
</TabItem> */}
|
||||
<TabItem value="pnpm" label="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm dlx create-medusa-app@latest
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
<Details>
|
||||
<Summary>Command Options</Summary>
|
||||
|
||||
The `create-medusa-app` command can accept the following options:
|
||||
|
||||
- `--repo-url <url>`: The repository URL to create the project from. By default it will be `https://github.com/medusajs/medusa-starter-default`.
|
||||
- `--seed`: A flag indicating whether the database should be seeded with demo data. By default, seeding is disabled.
|
||||
- `--no-boilerplate`: A flag that removes all files added for an enhanced onboarding experience (files under `src/admin`, `src/api`, etc...). This is helpful if you want to create a clean project, and is only recommended if you're familiar with Medusa.
|
||||
- `--no-browser`: Disables opening the browser at the end of the project creation and only shows success message.
|
||||
- `--skip-db`: Skips creating the database, running migrations, and seeding, and subsequently skips opening the browser. Useful if you want to set the database URL at a later point in the configurations.
|
||||
- `--db-url <url>`: Skips database creation and sets the database URL to the provided URL. Throws an error if connection to the database fails. Will still run migrations and open the admin after project creation. Useful if you already have a database created, locally or remotely.
|
||||
- `--no-migrations`: Skips running migrations, creating admin user, and seeding. If used, it's expected that you pass the `--db-url` option with a URL of a database that has all necessary migrations. Otherwise, unexpected errors will occur. Helpful only if combined with `--db-url`.
|
||||
- `--directory-path <path>`: Allows specifying the parent directory path to create the directory of the new project in.
|
||||
- `--with-nextjs-starter`: Installs the Next.js starter storefront under the `<PROJECT_NAME>-storefront` directory, where `<PROJECT_NAME>` is the name of the project you enter in the first question. If the `<PROJECT_NAME>-storefront` directory already exists, random characters are added at the end of `<PROJECT_NAME>-storefront`.
|
||||
</Details>
|
||||
|
||||
<Details className="border-t-0">
|
||||
<Summary>Example: Connect to a Vercel PostgreSQL Database</Summary>
|
||||
|
||||
If you want to use a PostgreSQL database hosted on Vercel, you must use the `--db-url` option and add to the end of your connection URL `?sslmode=require`. For example:
|
||||
|
||||
```bash
|
||||
npx create-medusa-app@latest --db-url "postgres://default:<password><host-region>.postgres.vercel-storage.com:5432/verceldb?sslmode=require"
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
If the database already has the necessary migrations and you don't need the command to run migrations, you can pass the `--no-migrations` option.
|
||||
|
||||
:::
|
||||
</Details>
|
||||
|
||||
<Details className="border-t-0">
|
||||
<Summary>Example: Connect to a Supabase Database</Summary>
|
||||
|
||||
If you want to connect to a Supabase database, you must use the `--db-url` option with its value being the connection URL to your Supabase database. For example:
|
||||
|
||||
```bash
|
||||
npx create-medusa-app@latest --db-url "postgres://postgres.<host>:<password>@aws-0-eu-central-1.pooler.supabase.com:5432/postgres"
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
If the database already has the necessary migrations and you don't need the command to run migrations, you can pass the `--no-migrations` option.
|
||||
|
||||
:::
|
||||
</Details>
|
||||
|
||||
## Step 2: Specify Project Name
|
||||
|
||||
You’ll first be asked to enter the name of your project, which is used to create the directory holding your Medusa backend. You can use the default `my-medusa-store` or enter another project name.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Enter an admin email
|
||||
|
||||
You'll then be prompted to enter an admin email for your admin user. You'll be using this admin email later to login to your admin dashboard. You can use the default `admin@medusa-test.com` or enter any other email.
|
||||
|
||||
---
|
||||
|
||||
## (Optional) Step 4: Install Next.js Starter Storefront
|
||||
|
||||
If you didn't pass the `--with-nextjs-starter` option, you'll be asked if you want to install the [Next.js starter storefront](./starters/nextjs-medusa-starter.mdx) along with the Medusa backend. This will install the storefront under the `<PROJECT_NAME>-storefront` directory, where `<PROJECT_NAME>` is the name of the project you specified in step 2.
|
||||
|
||||
If you want to install the storefront, type `y` and press <kbd>enter</kbd>. If not, you can use the default value `N` and just press <kbd>enter</kbd>.
|
||||
|
||||
:::note
|
||||
|
||||
You can always install the storefront at a later point. Medusa is a headless backend, so it operates without a storefront by default. You can connect any storefront to it. The Next.js Starter storefront is a good option to use, but you can also build your own storefront later on.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## (Optional) Step 5: Specify PostgreSQL credentials
|
||||
|
||||
:::note
|
||||
|
||||
This doesn't apply if you supplied the `--db-url` option. In that case, the command will fail if it can't connect to the supplied connection URL.
|
||||
|
||||
:::
|
||||
|
||||
By default, this command will try to use the default PostgreSQL credentials to connect to your PostgreSQL server. If they don't work, you'll be prompted to enter your PostgreSQL database and password. If they work, you can move ahead to the next step.
|
||||
|
||||
These credentials will be used to create a database during this setup and configure your Medusa backend to connect to that database.
|
||||
|
||||
After following the above steps, the project setup will start which includes:
|
||||
|
||||
1. Creating the project directory. The directory name will be the project name you entered in step 1.
|
||||
2. Creating the project database if `--db-url` and `--skip-db` options are not passed.
|
||||
3. Installing dependencies in your project directory.
|
||||
4. Building project
|
||||
5. Running migrations to migrate the Medusa schema into your project database unless `--skip-db` or `--no-migrations` are passed.
|
||||
6. Creating the admin user unless `--skip-db` or `--no-migrations` are passed.
|
||||
7. Seeding the database with demo data unless `--skip-db` or `--no-migrations` are passed.
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Log into admin dashboard
|
||||
|
||||
Once the project is prepared, the Medusa backend will start and the admin dashboard will be opened in your default browser. You'll then be asked to enter a password for the admin email you entered earlier, as well as other account information.
|
||||
|
||||
Once you're logged in, you can start using Medusa! Try following the setup guide to create your first product and order.
|
||||
|
||||
<Feedback
|
||||
event="survey_create-medusa-app"
|
||||
question="Did you set up Medusa successfully?"
|
||||
positiveQuestion="Is there anything that should improved?"
|
||||
negativeQuestion="Please describe the issue you faced."
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## Next Steps: Start your Development
|
||||
|
||||
Based on what you're building, you can find a development path for you in the Recipes page.
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/recipes',
|
||||
label: 'Recipes',
|
||||
customProps: {
|
||||
icon: Icons['map'],
|
||||
description: "Find learning paths based on what you're building"
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/overview',
|
||||
label: 'Medusa Backend Basics',
|
||||
customProps: {
|
||||
icon: Icons['server-stack-solid'],
|
||||
description: 'Learn all the basics of developing with the Medusa backend.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/overview',
|
||||
label: 'Commerce Modules',
|
||||
customProps: {
|
||||
icon: Icons['puzzle-solid'],
|
||||
description: 'Learn about the available commerce modules and features in the Medusa backend.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<DetailsList
|
||||
sections={[
|
||||
{
|
||||
title: "Errors when using VSCode or GitHub Codespaces",
|
||||
content: <ForwardingError />
|
||||
},
|
||||
{
|
||||
title: 'Can\'t connect to database with --db-url option',
|
||||
content: <DbUrlError />
|
||||
},
|
||||
{
|
||||
title: 'Error: EADDRINUSE',
|
||||
content: <EaddrinuseSection />
|
||||
},
|
||||
{
|
||||
title: "Can't Connect to PostgreSQL Docker Container",
|
||||
content: <PostgresDockerError />
|
||||
},
|
||||
{
|
||||
title: "EAGAIN error",
|
||||
content: <EagainError />
|
||||
},
|
||||
{
|
||||
title: "TypeError: cmd is not a function",
|
||||
content: <TypeErrorSection />
|
||||
},
|
||||
{
|
||||
title: "Error: connect ECONNREFUSED ::1:5432",
|
||||
content: <ConnectionErrorSection />
|
||||
},
|
||||
{
|
||||
title: "Invalid Token Error after Using --no-browser option",
|
||||
content: <InvalidTokenError />
|
||||
},
|
||||
{
|
||||
title: 'AwilixResolutionError: Could Not Resolve X',
|
||||
content: <FreshInstallationSection />
|
||||
},
|
||||
{
|
||||
title: "Other Errors",
|
||||
content: <OtherErrorsSection />
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,206 +0,0 @@
|
||||
---
|
||||
description: 'Learn step-by-step.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import DetailsList from '@site/src/components/DetailsList'
|
||||
import CorsErrorSection from '../../troubleshooting/cors-issues.md'
|
||||
|
||||
# Deploy Admin to Vercel
|
||||
|
||||
In this document, you’ll learn how to deploy the admin dashboard to Vercel.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Components
|
||||
|
||||
You must have a [Medusa backend](../../development/backend/install.mdx) installed along with an [admin dashboard plugin](../../admin/quickstart.mdx).
|
||||
|
||||
### Required Accounts
|
||||
|
||||
- [Vercel Account](https://vercel.com/)
|
||||
- [GitHub Account](https://github.com/): Only required if you’re deploying through the Vercel website.
|
||||
|
||||
:::note
|
||||
|
||||
If you want to use another Git Provider, it’s possible to follow along with this guide, but you’ll have to perform the equivalent steps in your Git Provider.
|
||||
|
||||
:::
|
||||
|
||||
### Required Tools
|
||||
|
||||
- [Git CLI](../../development/backend/prepare-environment.mdx#git): Only required if you’re deploying through the Vercel website.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create GitHub Repository
|
||||
|
||||
:::note
|
||||
|
||||
This step is only required if you’re deploying from the Vercel website. However, it’s highly recommended to connect your Vercel project to a Git repository for a better developer experience.
|
||||
|
||||
:::
|
||||
|
||||
Before you can deploy your admin dashboard, you need to create a GitHub repository and push the code base to it. To do that:
|
||||
|
||||
1. On GitHub, click the plus icon at the top right, then click New Repository.
|
||||
2. You’ll then be redirected to a new page with a form. In the form, enter the Repository Name.
|
||||
3. Scroll down and click Create repository.
|
||||
|
||||
### Push Code to GitHub Repository
|
||||
|
||||
The next step is to push the code to the GitHub repository you just created.
|
||||
|
||||
After creating the repository, you’ll be redirected to the repository’s page. On that page, you should see a URL that you can copy to connect your repository to a local directory.
|
||||
|
||||
Copy the link. Then, open your terminal in the directory that holds your Medusa backend codebase and run the following commands:
|
||||
|
||||
```bash
|
||||
git init
|
||||
git remote add origin <GITHUB_URL>
|
||||
```
|
||||
|
||||
Where `<GITHUB_URL>` is the URL you just copied.
|
||||
|
||||
Then, add, commit, and push the changes into the repository:
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "initial commit"
|
||||
git push
|
||||
```
|
||||
|
||||
After pushing the changes, you can find the files in your GitHub repository.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Configure Build Script
|
||||
|
||||
In the `package.json` of the Medusa backend, add or change a build script for the admin:
|
||||
|
||||
```json
|
||||
"scripts": {
|
||||
// other scripts
|
||||
"build:admin": "medusa-admin build --deployment",
|
||||
}
|
||||
```
|
||||
|
||||
Note that when using the `--deployment` option, the backend's URL is loaded from the `MEDUSA_ADMIN_BACKEND_URL` environment variable. You'll configure this environment variable in a later step.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Add Vercel Configurations
|
||||
|
||||
In the root directory of the Medusa backend, create a new file `vercel.json` with the following content:
|
||||
|
||||
```json
|
||||
{
|
||||
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Push Changes to GitHub
|
||||
|
||||
After making all the previous changes, push them to GitHub before starting the deployment on Vercel:
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "prepare repository for deployment"
|
||||
git push
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Deploy to Vercel
|
||||
|
||||
This section covers how to deploy the admin, either using the Vercel website or using Vercel’s CLI tool.
|
||||
|
||||
### Option 1: Using the Vercel Website
|
||||
|
||||
This section explains how to deploy the admin using the Vercel website:
|
||||
|
||||
1. Open the [Vercel dashboard](https://vercel.com/dashboard) after logging in.
|
||||
2. Click on the “Add New…” button next to the search bar.
|
||||
3. Choose Project from the dropdown.
|
||||
4. In the new page that opens, find the Git repository that holds your Medusa backend and click on the Import button. If you haven’t connected your Vercel account to any Git provider, you must do that first.
|
||||
5. In the Configure Project form:
|
||||
1. Set the Framework Preset to Other.
|
||||
2. Open the Build and Output Settings collapsible, and set the Build Command to `yarn build:admin` and the Output Directory to `build`.
|
||||
3. Open the Environment Variables collapsible, and add an environment variable with the name `MEDUSA_ADMIN_BACKEND_URL` with the value being the URL to your deployed Medusa backend.
|
||||
4. You can optionally edit the Project Name.
|
||||
6. Once you’re done, click on the “Deploy” button.
|
||||
|
||||
This will start the deployment of the admin. Once it’s done, you’ll be redirected to the main dashboard of your new project.
|
||||
|
||||
:::note
|
||||
|
||||
At this point, when you visit the admin, you will face errors related to Cross-Origin Resource Sharing (CORS) while using the admin. Before you start using the admin, follow along the [Configure CORS on the Medusa Backend](#step-6-configure-cors-on-the-medusa-backend) section.
|
||||
|
||||
:::
|
||||
|
||||
### Option 2: Using Vercel’s CLI Tool
|
||||
|
||||
This section explains how to deploy the admin using the Vercel CLI tool. You should have the CLI tool installed first, as explained in [Vercel’s documentation](https://vercel.com/docs/cli).
|
||||
|
||||
In the directory of your Medusa backend, run the following command to deploy your admin:
|
||||
|
||||
```bash
|
||||
vercel --build-env MEDUSA_ADMIN_BACKEND_URL=<YOUR_BACKEND_URL>
|
||||
```
|
||||
|
||||
Where `<YOUR_BACKEND_URL>` is the URL of your deployed Medusa backend.
|
||||
|
||||
You’ll then be asked to log in if you haven’t already, and to choose the scope to deploy your project to. You can also decide to link the admin to an existing project, or change the project’s name.
|
||||
|
||||
When asked, ”In which directory is your code located?”, keep the default `./` and just press Enter.
|
||||
|
||||
The project setup will then start. When asked if you want to modify the settings, answer `y`. You’ll then be asked a series of questions:
|
||||
|
||||
1. “Which settings would you like to overwrite”: select Build Command and Output Directory using the space bar, then press Enter.
|
||||
2. “What's your **Build Command**?”: enter `yarn build:admin`.
|
||||
3. “What's your **Output Directory**?”: enter `build`.
|
||||
|
||||
After that, it will take a couple of minutes for the deployment to finish. The link to the admin will be shown in the final output of the command.
|
||||
|
||||
:::note
|
||||
|
||||
At this point, when you visit the admin, you will face errors related to Cross-Origin Resource Sharing (CORS) while using the admin. Before you start using the admin, follow along the [Configure CORS on the Medusa Backend](#step-6-configure-cors-on-the-medusa-backend) section.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Configure CORS on the Medusa Backend
|
||||
|
||||
To send requests to the Medusa backend from the admin dashboard, you must set the `ADMIN_CORS` environment variable on your backend to the admin’s URL.
|
||||
|
||||
:::note
|
||||
|
||||
If you want to set a custom domain to your admin dashboard on Vercel, make sure to do it before this step. You can refer to this guide on [Vercel’s documentation](https://vercel.com/docs/concepts/projects/domains/add-a-domain).
|
||||
|
||||
:::
|
||||
|
||||
On your Medusa backend, add the following environment variable:
|
||||
|
||||
```bash
|
||||
ADMIN_CORS=<ADMIN_URL>
|
||||
```
|
||||
|
||||
Where `<ADMIN_URL>` is the URL of your admin dashboard that you just deployed.
|
||||
|
||||
Then, restart your Medusa backend. Once the backend is running again, you can use your admin dashboard.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<DetailsList
|
||||
sections={[
|
||||
{
|
||||
title: 'CORS Error',
|
||||
content: <CorsErrorSection />
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,126 +0,0 @@
|
||||
---
|
||||
description: "General steps for deploying the admin."
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import DetailsList from '@site/src/components/DetailsList'
|
||||
import CorsErrorSection from '../../troubleshooting/cors-issues.md'
|
||||
|
||||
# General Deployment Guide for Medusa Admin
|
||||
|
||||
In this guide, you’ll learn the general steps to follow when deploying the Medusa Admin separately from the backend. This guide isn’t tailored towards any hosting provider.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Project
|
||||
|
||||
Before you deploy the admin, you need a Medusa project with the backend and admin plugin. You can refer to [this guide to learn how to install it](../../create-medusa-app.mdx).
|
||||
|
||||
### Deployed Medusa Backend
|
||||
|
||||
The deployed admin must connect to a publicly deployed backend. So, deploy the backend first to obtain its URL.
|
||||
|
||||
You can follow one of [these backend deployment guides](../server/index.mdx).
|
||||
|
||||
---
|
||||
|
||||
## (Optional) Step 1: Create GitHub Repository
|
||||
|
||||
Many hosting providers allow you to deploy your project directly from GitHub. This makes it easier for you to push changes and updates without having to manually trigger the update in the hosting provider.
|
||||
|
||||
If your hosting provider supports that, create a GitHub repository and push the Medusa backend project’s code to it:
|
||||
|
||||
:::note
|
||||
|
||||
Even though you’re just deploying the admin, you must include the entire Medusa backend project in the deployed repository. The build process of the admin uses many of the backend’s dependencies.
|
||||
|
||||
:::
|
||||
|
||||
```bash
|
||||
git init
|
||||
git remote add origin <GITHUB_URL>
|
||||
git add .
|
||||
git commit -m "initial commit"
|
||||
git push
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Configure Build Command
|
||||
|
||||
In the `package.json` of the Medusa backend, add or change a build script for the admin:
|
||||
|
||||
```json title="package.json"
|
||||
"scripts": {
|
||||
// other scripts
|
||||
"build:admin": "medusa-admin build --deployment",
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
When using the `--deployment` option, the backend's URL is loaded from the `MEDUSA_ADMIN_BACKEND_URL` environment variable. You'll configure this environment variable in a later step.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Deploy to Hosting Provider
|
||||
|
||||
The steps to deploy the admin can be different based on the hosting provider you use. The following points cover common configurations across hosting providers:
|
||||
|
||||
- If your hosting provider supports choosing a Framework Preset, choose the “Other” option as the Medusa Admin doesn’t follow known framework presets.
|
||||
- Set the build command of your deployed project to use the `build:admin` command:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build:admin
|
||||
```
|
||||
|
||||
- Set the output directory of your deployed project to `build`.
|
||||
- Add the environment variable `MEDUSA_ADMIN_BACKEND_URL` and set its value to the URL of your deployed Medusa backend.
|
||||
- If your hosting provider supports URL rewrites, add a configuration to rewrite the `/(.*)` URL pattern to `/index.html`. For example, if you’re deploying to Vercel you add the following in `vercel.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Configure CORS on the Deployed Backend
|
||||
|
||||
To send requests from the admin dashboard to the Medusa backend, you must set the `ADMIN_CORS` environment variable on your backend to the admin’s URL:
|
||||
|
||||
```bash
|
||||
ADMIN_CORS=<ADMIN_URL>
|
||||
```
|
||||
|
||||
Where `<ADMIN_URL>` is the URL of your admin dashboard that you just deployed.
|
||||
|
||||
Then, restart your Medusa backend. Once the backend is running again, you can use your admin dashboard.
|
||||
|
||||
---
|
||||
|
||||
## Step 5: (Optional) Create Admin User
|
||||
|
||||
To log in to the admin dashboard, you must have an admin user. To create one, run the following command on your deployed backend:
|
||||
|
||||
```bash
|
||||
npx medusa user --email admin@medusa-test.com --password supersecret
|
||||
```
|
||||
|
||||
You can then log in using the specified email and password.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<DetailsList
|
||||
sections={[
|
||||
{
|
||||
title: 'CORS Error',
|
||||
content: <CorsErrorSection />
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
hide_table_of_contents: true
|
||||
description: 'Learn how to deploy the Medusa Admin to different hosting providers to be used with a deployed Medusa backend.'
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
|
||||
# Medusa Admin Deployment Guides
|
||||
|
||||
Find in this page guides to deploy your Medusa Admin on different platforms.
|
||||
|
||||
:::note
|
||||
|
||||
These guides are for deploying the admin separately from the backend. If you want to deploy them together, you can refer to the [deployment guides of the backend](../server/index.mdx). Learn more [here](../index.mdx#deploying-the-medusa-admin).
|
||||
|
||||
:::
|
||||
|
||||
<DocCardList />
|
||||
@@ -1,10 +0,0 @@
|
||||
.osTabs {
|
||||
background-color: #f4f4f4;
|
||||
padding: 10px;
|
||||
border-radius: 6.4px;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.osTabs li:not([aria-selected=true]) {
|
||||
color: #000;
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
---
|
||||
description: "In this document, learn about the general approach of deploying a Medusa project. This includes how to deploy the backend, admin, and storefront."
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import { getCategoryItems } from '@site/src/utils/get-first-category-item';
|
||||
|
||||
# Deployment Overview
|
||||
|
||||
In this document, you’ll learn about the different ways you can deploy your Medusa project.
|
||||
|
||||
## Overview
|
||||
|
||||
A standard Medusa project is made up of the following:
|
||||
|
||||
- Medusa backend
|
||||
- Medusa Admin
|
||||
- One or more storefronts
|
||||
|
||||

|
||||
|
||||
This guide details options to consider when deploying each of these components in your Medusa project.
|
||||
|
||||
---
|
||||
|
||||
## Deploying the Medusa Backend
|
||||
|
||||
You must deploy the Medusa backend before the admin or storefront, as both of them connect to the backend and won’t work without a deployed Medusa backend URL.
|
||||
|
||||

|
||||
|
||||
The Medusa backend is a Node.js server. So, it must be deployed to a hosting provider that supports deploying servers, such as Railway, DigitalOcean, AWS, Heroku, etc…
|
||||
|
||||
:::tip
|
||||
|
||||
For optimal experience, make sure that the hosting provider and plan offer at least 2GB of RAM.
|
||||
|
||||
:::
|
||||
|
||||
Your backend connects to PostgreSQL and Redis databases. Most hosting providers support deploying and managing these databases along with your Medusa backend (such as Railway and DigitalOcean). You can also choose a separate hosting for either of them and connect to them with Medusa’s configurations.
|
||||
|
||||
<DocCardList
|
||||
items={getCategoryItems({
|
||||
categoryCustomId: "deploy_backend"
|
||||
})}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## Deploying the Medusa Admin
|
||||
|
||||
There are two options to deploy the Medusa Admin:
|
||||
|
||||
### Deploy Admin with Backend
|
||||
|
||||
Since the Medusa Admin is a plugin installed in the backend, you may choose to host it along with the backend.
|
||||
|
||||
In this scenario, make sure the hosting provider and plan of your choice provide at least 2GB of RAM, as the admin build requires high RAM usage.
|
||||
|
||||

|
||||
|
||||
The backend deployment guides mention details on how to deploy the admin along with the backend.
|
||||
|
||||
### Deploy Admin through GitHub Action
|
||||
|
||||
Alternatively, if you host your backend’s code on GitHub, you can:
|
||||
|
||||
1. Disable the [`autoRebuild` option](../admin/configuration.mdx#plugin-options) of the admin plugin.
|
||||
2. Create a GitHub action that builds the admin before the deployment is triggered.
|
||||
|
||||
With this solution, the hosting provider doesn’t handle the admin building, decreasing the RAM usage.
|
||||
|
||||

|
||||
|
||||
### Deploy Admin Separately
|
||||
|
||||
You may choose to deploy the admin into a separate hosting provider or instance. The admin can be hosted on providers that support front-end websites and frameworks, such as Vercel.
|
||||
|
||||
:::note
|
||||
|
||||
As per Vercel’s [license and plans](https://vercel.com/pricing), their free plan can only be used for personal, non-commercial projects. So, you can deploy the admin on the free plan for development purposes, but for commercial projects, you must update your Vercel plan.
|
||||
|
||||
:::
|
||||
|
||||
<DocCardList
|
||||
items={getCategoryItems({
|
||||
categoryCustomId: "deploy_admin"
|
||||
})}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## Deploying the Storefront
|
||||
|
||||
The storefront is deployed separately from the Medusa backend, and the hosting options depend on the tools and frameworks you use to create the storefront.
|
||||
|
||||
If you’re using the Next.js starter, you may deploy the storefront to any hosting provider that supports frontend frameworks, such as Vercel.
|
||||
|
||||
:::note
|
||||
|
||||
As per Vercel’s [license and plans](https://vercel.com/pricing), their free plan can only be used for personal, non-commercial projects. So, you can deploy the storefront on the free plan for development purposes, but for commercial projects, you must update your Vercel plan.
|
||||
|
||||
:::
|
||||
|
||||
<DocCardList
|
||||
items={getCategoryItems({
|
||||
categoryCustomId: "deploy_storefront"
|
||||
})}
|
||||
/>
|
||||
@@ -1,386 +0,0 @@
|
||||
---
|
||||
description: 'Learn step-by-step.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# Deploy Your Medusa Backend to DigitalOcean Apps
|
||||
|
||||
In this document, you'll learn how to deploy your Medusa backend to a DigitalOcean App.
|
||||
|
||||
DigitalOcean is a reliable hosting provider that provides different ways to host websites and servers. One way to host a backend is using their DigitalOcean App Platform.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Backend
|
||||
|
||||
It is assumed that you already have a Medusa backend installed locally. If you don’t, please follow the [quickstart guide](../../create-medusa-app.mdx).
|
||||
|
||||
Furthermore, your Medusa backend should be configured to work with PostgreSQL and Redis. You can follow the [Configure your Backend documentation](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx) to learn how to do that.
|
||||
|
||||
### Needed Accounts
|
||||
|
||||
- A [DigitalOcean](https://cloud.digitalocean.com/registrations/new) account. You need to provide a payment method on sign up.
|
||||
- A [GitHub](https://github.com/) account to create a repository to host your Medusa backend’s codebase.
|
||||
|
||||
:::tip
|
||||
|
||||
If you want to use another Git Provider supported by DigitalOcean, it’s possible to follow along with this guide but you’ll have to perform the equivalent steps in your Git Provider.
|
||||
|
||||
:::
|
||||
|
||||
### Required Tools
|
||||
|
||||
- Git’s CLI tool. You can follow [this documentation to learn how to install it for your operating system](../../development/backend/prepare-environment.mdx#git).
|
||||
|
||||
---
|
||||
|
||||
## Changes to package.json
|
||||
|
||||
Change the `start` script in `package.json` to the following:
|
||||
|
||||
```json
|
||||
"start": "medusa migrations run && medusa start"
|
||||
```
|
||||
|
||||
This ensures that Migrations are run every time the Medusa backend is restarted.
|
||||
|
||||
---
|
||||
|
||||
## Changes to medusa-config.js
|
||||
|
||||
In `medusa-config.js`, the `DATABASE_URL` variable is set to the environment variable `DATABASE_URL`. This needs to be changed as DigitalOcean provides the different details of the database connection separately.
|
||||
|
||||
Replace the previous declaration of `DATABASE_URL` in `medusa-config.js` with the following:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const DB_USERNAME = process.env.DB_USERNAME
|
||||
const DB_PASSWORD = process.env.DB_PASSWORD
|
||||
const DB_HOST = process.env.DB_HOST
|
||||
const DB_PORT = process.env.DB_PORT
|
||||
const DB_DATABASE = process.env.DB_DATABASE
|
||||
|
||||
const DATABASE_URL =
|
||||
`postgres://${DB_USERNAME}:${DB_PASSWORD}` +
|
||||
`@${DB_HOST}:${DB_PORT}/${DB_DATABASE}`
|
||||
```
|
||||
|
||||
In addition, you must add to `projectConfig` in the exported object a new property `database_extra`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
projectConfig: {
|
||||
// ...
|
||||
database_extra: { ssl: { rejectUnauthorized: false } },
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Also, if you're planning on using scheduled jobs, you need to set the `redis_url` configurations:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
projectConfig: {
|
||||
// ...
|
||||
redis_url: process.env.REDIS_URL,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Changes to .gitignore
|
||||
|
||||
Your `.gitignore` may have `yarn.lock` and `package-lock.json` in it. If so, remove both or one of them to ensure they're committed with your code. Otherwise, you may face build errors after deployment.
|
||||
|
||||
---
|
||||
|
||||
## (Optional) Configure the Admin
|
||||
|
||||
If you're using the Medusa Admin plugin, you have two options to deploy it: either with the backend or separately.
|
||||
|
||||
### Deploying with the Backend
|
||||
|
||||
To deploy the admin with the backend:
|
||||
|
||||
1. Your chosen plan must offer at least 2GB of RAM.
|
||||
2. Enable the [autoRebuild option](../../admin/configuration.mdx#plugin-options) of the admin plugin:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: "@medusajs/admin",
|
||||
/** @type {import('@medusajs/admin').PluginOptions} */
|
||||
options: {
|
||||
autoRebuild: true,
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
Alternatively, you can use a GitHub action to build the admin as explained [here](../index.mdx#deploy-admin-through-github-action).
|
||||
|
||||
### Deploying Separately
|
||||
|
||||
If you choose to deploy the admin separately, disable the admin plugin's [serve option](../../admin/configuration.mdx#plugin-options):
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: "@medusajs/admin",
|
||||
/** @type {import('@medusajs/admin').PluginOptions} */
|
||||
options: {
|
||||
// only enable `serve` in development
|
||||
// you may need to add the NODE_ENV variable
|
||||
// manually
|
||||
serve: process.env.NODE_ENV === "development",
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
This ensures that the admin isn't built or served in production. You can also change `@medusajs/admin` dependency to be a dev dependency in `package.json`.
|
||||
|
||||
You can alternatively remove the admin plugin for the plugins array.
|
||||
|
||||
:::tip
|
||||
|
||||
Refer to the [admin deployment guides on how to deploy the admin separately](../admin/index.mdx).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## (Optional) Use Production Modules
|
||||
|
||||
If you're using development modules, such as the Local Event Bus or the In-Memory Cache Modules, it's highly recommended to use a production module instead.
|
||||
|
||||
Medusa provides a [Redis Event Bus](../../development/events/modules/redis.md) and a [Redis Cache Module](../../development/cache/modules/redis.md) that you can install and configure. Alternatively, you can use custom modules that integrate other services for these functionalities.
|
||||
|
||||
---
|
||||
|
||||
## Create GitHub Repository
|
||||
|
||||
Before you can deploy your Medusa backend you need to create a GitHub repository and push the code base to it.
|
||||
|
||||
On GitHub, click the plus icon at the top right, then click New Repository.
|
||||
|
||||
You’ll then be redirected to a new page with a form. In the form, enter the Repository Name then scroll down and click Create repository.
|
||||
|
||||
### Push Code to GitHub Repository
|
||||
|
||||
The next step is to push the code to the GitHub repository you just created.
|
||||
|
||||
After creating the repository, you’ll be redirected to the repository’s page. On that page, you should see a URL that you can copy to connect your repository to a local directory.
|
||||
|
||||
Copy the link. Then, open your terminal in the directory that holds your Medusa backend codebase and run the following commands:
|
||||
|
||||
```bash
|
||||
git init
|
||||
git remote add origin <GITHUB_URL>
|
||||
git branch -M <BRANCH>
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `<GITHUB_URL>` is the URL you just copied.
|
||||
- `<BRANCH>` is the name of your default branch. Typically, it's either `main` or `master`. If you're unsure, you can find it on the repository's main page.
|
||||
|
||||
Then, add, commit, and push the changes into the repository:
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "initial commit"
|
||||
git push origin <BRANCH>
|
||||
```
|
||||
|
||||
Where, `<BRANCH>` is the default repository branch.
|
||||
|
||||
After pushing the changes, you can find the files in your GitHub repository.
|
||||
|
||||
---
|
||||
|
||||
## Deploy to DigitalOcean App
|
||||
|
||||
After logging into your account, click on the Create button at the top right, then choose Apps.
|
||||
|
||||
### Choose Repository
|
||||
|
||||
In the Create App page, choose GitHub from the Service Provider list.
|
||||
|
||||
If you haven’t given DigitalOcean access before, click on Manage Access under Source Code. You’ll then be redirected to GitHub to give DigitalOcean access.
|
||||
|
||||
Once DigitalOcean has access to your GitHub account, you should see a Repository input. Click on it and search for the repository you created earlier.
|
||||
|
||||
Additional inputs will show up to choose the Branch, Source Directory, and Autodeploy options.
|
||||
|
||||
If you host your Medusa backend in a monorepo, you should change the Source Directory to the directory the backend is available in the repository. Otherwise, it can be left as is.
|
||||
|
||||
Once you’re done, click Next to move on to the next step.
|
||||
|
||||
### Specify Web Service Resources
|
||||
|
||||
In the next step, you’ll see the resources to create.
|
||||
|
||||
:::note
|
||||
|
||||
If you have a Dockerfile available in the backend’s codebase, you’ll have two resources showing. You can remove it by clicking on the trash icon at the right of the resource.
|
||||
|
||||
:::
|
||||
|
||||
Click on the Edit button next to the Web Service, and ensure the following information is set correctly:
|
||||
|
||||
- Resource Type is set to Node.js
|
||||
- HTTP Port is set to 9000
|
||||
- HTTP Request Routes is set to `/`
|
||||
|
||||
Once you've set up everything correctly, click the Back button at the end of the form.
|
||||
|
||||
### Specify Database Resources
|
||||
|
||||
On the same page, expand the Add Resources section, choose Database, then click Add.
|
||||
|
||||
In the new page, you’ll be shown a PostgreSQL database to be created. Notice that it’s important to choose a name that you’ll remember as you’ll need the name in next steps. You can leave the name as is if it’s not necessary to change it.
|
||||
|
||||
Once you’re done, click Create and Attach. You’ll be redirected back to the previous page with the database added to the resources.
|
||||
|
||||
Once you’re done, click Next to move on to the next step.
|
||||
|
||||
### Set Environment Variables
|
||||
|
||||
In this section, you’ll add environment variables that are essential to your Medusa backend.
|
||||
|
||||
You should see two ways to add environment variables: Global or specific to the Web Service.
|
||||
|
||||
Click Edit on the second row to add environment variables specific to the Web Service. Add the following environment variables:
|
||||
|
||||
```bash
|
||||
DB_USERNAME=${db.USERNAME}
|
||||
DB_PASSWORD=${db.PASSWORD}
|
||||
DB_HOST=${db.HOSTNAME}
|
||||
DB_PORT=${db.PORT}
|
||||
DB_DATABASE=${db.DATABASE}
|
||||
REDIS_URL=${redis.DATABASE_URL}
|
||||
JWT_SECRET=secret
|
||||
COOKIE_SECRET=secret
|
||||
NPM_CONFIG_PRODUCTION=false
|
||||
YARN_PRODUCTION=false
|
||||
NODE_ENV=production
|
||||
```
|
||||
|
||||
:::warning
|
||||
|
||||
It’s highly recommended to use strong, randomly generated secrets for `JWT_SECRET` and `COOKIE_SECRET`.
|
||||
|
||||
:::
|
||||
|
||||
Notice how for database environment variables you access the values from the database you created earlier `db`. If you changed the name of the database, you must change `db` here to the name you supplied to the PostgreSQL database.
|
||||
|
||||
Another thing to note here is that you added a `REDIS_URL` environment variable that uses a `redis` resource to retrieve the URL. Redis is necessary if you're using Scheduled Jobs and if you're using Redis modules, such as the Redis Event Bus module. You’ll be creating the Redis resource in a later section.
|
||||
|
||||
If you're using modules that require setting other environment variables, make sure to set them here. You can also add them later.
|
||||
|
||||
Once you’re done click Save, then click Next to proceed to the next section.
|
||||
|
||||
### Finalize App
|
||||
|
||||
In the next section, you’ll be shown the app info and the region it will be deployed to. You can leave it all as is or make changes if you find it necessary.
|
||||
|
||||
Once you’re done, click Next to go to the next step.
|
||||
|
||||
In the final step, you can see a review of everything you created. If everything looks good, scroll down and click Create Resource.
|
||||
|
||||
### Create Redis Resource
|
||||
|
||||
While the backend is being deployed, you can create the Redis resource. To do that:
|
||||
|
||||
1. Click the Create button at the top right and choose Database from the dropdown.
|
||||
2. In the new page under Choose a database engine, choose Redis.
|
||||
3. Scroll down to the “Choose a unique database cluster name” input. Since you used the name `redis` in the `REDIS_URL` environment variables, change the value to `redis` here.
|
||||
4. Once you’re done, click on Create Database Cluster.
|
||||
|
||||
### Attach Redis Database
|
||||
|
||||
Once the Redis database is created, you need to attach it to your app. To do that:
|
||||
|
||||
1. Go back to the App you created earlier by choose Apps in the sidebar then clicking on the App name.
|
||||
2. Click at the white Create button at the top right and choose Create/Attach Database.
|
||||
3. In the new page, click on the Previously Created DigitalOcean Database radio button. Then, under Database Cluster select the Redis database you just created.
|
||||
4. Once you’re done click Attach Database. This will add the Redis database to the list of resources of your App and will trigger a redeploy of the App.
|
||||
|
||||
### Change Health Check Settings
|
||||
|
||||
By default, DigitalOcean will perform a health check immediately as the app runs. However, the Medusa backend requires some initial load-time before it's ready for a health check.
|
||||
|
||||
So, you must delay the app's health check to enure that the deployment doesn't fail. To do that:
|
||||
|
||||
1. On your app's page, click on the Settings page.
|
||||
2. From the tabs before the settings section, choose the name of your web service resource.
|
||||
3. Scroll down and find the "Health Checks" section, then click on "Edit" next to it.
|
||||
4. In the section that opens, expand the "Show Advanced Parameters" section.
|
||||
5. Find the "Initial Delay (s)" input and set its value to `300`.
|
||||
6. Once done, click on the Save button.
|
||||
|
||||
This will redeploy your app, which should finish successfully.
|
||||
|
||||
---
|
||||
|
||||
## Test your Backend
|
||||
|
||||
Once the redeployment is complete, copy the URL of the App which can be found under the App’s name.
|
||||
|
||||
Then, go to `<YOUR_APP_URL>/store/products`. If the deployment was successful, you should receive a JSON response.
|
||||
|
||||
### Health Route
|
||||
|
||||
You can access `/health` to get health status of your deployed backend.
|
||||
|
||||
### Testing the Admin
|
||||
|
||||
If you deployed the [admin dashboard with the backend](#deploying-with-the-backend), you can test it by going to `<YOUR_APP_URL>/app`. If you changed the admin path, make sure to change `/app` to the path you've set.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If errors occur in your deployment, you can find the logs by going to the Activity tab in your DigitalOcean App.
|
||||
|
||||
### ERROR: Failed to build / Project does not contain a package manager
|
||||
|
||||
If you find this error in your logs, make sure to remove `yarn.lock` or `package-lock.json` from the `.gitignore` file in your project, then commit the changes.
|
||||
|
||||
---
|
||||
|
||||
## Run Commands on Your Backend
|
||||
|
||||
To run commands on your backend, you can access the console on the App’s page by choosing the Console tab. This opens a console in your browser where you can run commands on your backend.
|
||||
|
||||
For example, you can run the following command to create a new admin user:
|
||||
|
||||
```bash
|
||||
npx medusa user --email <EMAIL> --password <PASSWORD>
|
||||
```
|
||||
|
||||
Make sure to replace `<EMAIL>` and `<PASSWORD>` with the credentials you want to give the user.
|
||||
|
||||
---
|
||||
|
||||
## Add Environment Variables
|
||||
|
||||
You’ll likely need to add environment variables later such as Admin Cross-Origin Resource Sharing (CORS) and Store CORS variables.
|
||||
|
||||
To add environment variables, on the App’s page click on Settings and choose the Web Service component.
|
||||
|
||||
Then, scroll down and find Environment Variables. You can expand the environment variables by clicking Edit on the right. Here, you can edit, add, and remove environment variables.
|
||||
|
||||
Once you click Save, the environment variables will be saved and a redeployment will be triggered.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Deploy the Medusa Admin](../admin/index.mdx).
|
||||
- [Deploy the Storefront](../storefront/index.mdx).
|
||||
@@ -1,390 +0,0 @@
|
||||
---
|
||||
description: 'Learn step-by-step.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# Deploy Your Medusa Backend on Heroku
|
||||
|
||||
In this document, you'll learn how to deploy your Medusa backend on Heroku. Heroku is a PaaS (Platform as a Service) that allows you to easily deploy your applications in the cloud.
|
||||
|
||||
Alternatively, you can use this button to deploy the Medusa backend to Heroku directly:
|
||||
|
||||
<a href="https://heroku.com/deploy?template=https://github.com/medusajs/medusa-starter-default/tree/feat/deploy-heroku" className="img-url">
|
||||
<img src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy to Heroku" className="no-zoom-img" />
|
||||
</a>
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Backend
|
||||
|
||||
It is assumed that you already have a Medusa backend installed locally. If you don’t, please follow the [quickstart guide](../../development/backend/install.mdx).
|
||||
|
||||
Furthermore, your Medusa backend should be configured to work with PostgreSQL and Redis. You can follow the [Configure your Backend documentation](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx) to learn how to do that.
|
||||
|
||||
### Needed Accounts
|
||||
|
||||
- A [Heroku](https://heroku.com/) account.
|
||||
|
||||
### Required Tools
|
||||
|
||||
- Git’s CLI tool. You can follow [this documentation to learn how to install it for your operating system](../../development/backend/prepare-environment.mdx#git).
|
||||
- Heroku's CLI tool. You can follow [Heroku's documentation to learn how to install it for your operating system](https://devcenter.heroku.com/articles/heroku-cli).
|
||||
|
||||
---
|
||||
|
||||
## Deploy to Heroku
|
||||
|
||||
### 1. Login to Heroku from your CLI
|
||||
|
||||
Before you can create an app with Heroku, you must login with the CLI tool:
|
||||
|
||||
```bash
|
||||
heroku login
|
||||
```
|
||||
|
||||
Depending on your operating system, you must follow either the instructions in your terminal or a page in your browser will open.
|
||||
|
||||
### 2. Create an App with Heroku
|
||||
|
||||
In the root directory of your Medusa backend, run the following commands to create an app on Heroku and add it as a remote origin:
|
||||
|
||||
```bash
|
||||
heroku create <APP_NAME>
|
||||
heroku git:remote -a <APP_NAME>
|
||||
```
|
||||
|
||||
Where `<APP_NAME>` is the name of the app you'll create. You can use any name you want.
|
||||
|
||||
### 3. Install Postgresql and Redis on Heroku
|
||||
|
||||
Medusa requires a Postgres database and a Redis instance to work. You can add those to your Heroku app using Add-ons.
|
||||
|
||||
:::note
|
||||
|
||||
If you don't have a payment method set up in your Heroku account, you'll be asked to enter your payment details when you try to install these addons.
|
||||
|
||||
:::
|
||||
|
||||
#### PostgreSQL
|
||||
|
||||
Add a Postgres add-on to your Heroku app with the following command:
|
||||
|
||||
```bash
|
||||
heroku addons:create heroku-postgresql:mini
|
||||
```
|
||||
|
||||
This uses Heroku Postgres's smallest plan. You can check out [the available plans and pricing of Heroku Postgres on Heroku's website.](https://elements.heroku.com/addons/heroku-postgresql#pricing)
|
||||
|
||||
#### Redis
|
||||
|
||||
Add a Redis instance to your Heroku app with the following command:
|
||||
|
||||
```bash
|
||||
heroku addons:create stackhero-redis:ist-m4euc0
|
||||
```
|
||||
|
||||
This uses the lowest plan in Stackhero Redis. You can check out [the plans and pricing of Stackhero Redis on Heroku's website.](https://elements.heroku.com/addons/stackhero-redis#pricing)
|
||||
|
||||
### 4. Configure Environment Variables on Heroku
|
||||
|
||||
Medusa requires a set of environment variables to be configured. You can learn more about Medusa's configurations in the [Configure your Medusa backend](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx) document.
|
||||
|
||||
Run the following commands in the root directory of your Medusa backend to set some environment variables:
|
||||
|
||||
```bash
|
||||
heroku config:set NODE_ENV=production
|
||||
heroku config:set JWT_SECRET=your-super-secret
|
||||
heroku config:set COOKIE_SECRET=your-super-secret-pt2
|
||||
heroku config:set NPM_CONFIG_PRODUCTION=false
|
||||
```
|
||||
|
||||
:::tip
|
||||
|
||||
Make sure to replace `your-super-secret` and `your-super-secret-pt2` with actual secrets in a production environment.
|
||||
|
||||
:::
|
||||
|
||||
#### Set Buildpack
|
||||
|
||||
Additionally, you need to set the buildpack to Node.js using the following command:
|
||||
|
||||
```bash
|
||||
heroku buildpacks:set heroku/nodejs
|
||||
```
|
||||
|
||||
#### Configure the Redis URL
|
||||
|
||||
Stackhero Redis adds the Redis URL under the environment variable `STACKHERO_REDIS_URL_TLS`. However, Medusa looks for the `REDIS_URL` environment variable when initializing the connection with Redis.
|
||||
|
||||
Retrieve the value of `STACKHERO_REDIS_URL_TLS` with the following command:
|
||||
|
||||
```bash
|
||||
heroku config:get STACKHERO_REDIS_URL_TLS
|
||||
```
|
||||
|
||||
This prints the value of the environment variable which is a Redis connection string.
|
||||
|
||||
:::note
|
||||
|
||||
If you see nothing, you have to wait until the creation process of the stackhero redis instance is done. Try waiting a few minutes then trying again.
|
||||
|
||||
:::
|
||||
|
||||
Copy that value and use it to set the environment variable `REDIS_URL` with the following command:
|
||||
|
||||
```bash
|
||||
heroku config:set REDIS_URL=<YOUR_REDIS_URL>
|
||||
```
|
||||
|
||||
Where `<YOUR_REDIS_URL>` is the value you received from the previous command.
|
||||
|
||||
#### Configure the PostgreSQL Database URL
|
||||
|
||||
If you're using the Heroku PostgreSQL Add-on, it should configure the environment variable `DATABASE_URL`. In that case, you don't need to perform any additional actions.
|
||||
|
||||
However, if you use another add-on, make sure to set the environment variable `DATABASE_URL` to the PostgreSQL Database URL.
|
||||
|
||||
#### (Optional) Configure Modules Environment Variables
|
||||
|
||||
If you use modules in your Medusa backend that require setting environment variables, then you should set them at this point.
|
||||
|
||||
For example, if you use the Redis Event Bus module:
|
||||
|
||||
```bash
|
||||
heroku config:set EVENTS_REDIS_URL=<YOUR_REDIS_URL>
|
||||
```
|
||||
|
||||
Make sure to change `EVENTS_REDIS_URL` to the environment variable name you use for your module's configurations.
|
||||
|
||||
If your module requires setting up other services, do that then add the environment variables.
|
||||
|
||||
#### (Optional) Configure CORS Variables
|
||||
|
||||
Optionally, if you've deployed the admin dashboard and you want to ensure it can use the backend's REST APIs, you must set the following environment variable:
|
||||
|
||||
```bash
|
||||
heroku config:set ADMIN_CORS=<YOUR_ADMIN_URL>
|
||||
```
|
||||
|
||||
Where `<YOUR_ADMIN_URL>` is the URL of your admin dashboard.
|
||||
|
||||
Similarly, if you've deployed the storefront and you want to ensure it can use the backend's REST APIs, you must set the following environment variable:
|
||||
|
||||
```bash
|
||||
heroku config:set STORE_CORS=<YOUR_STOREFRONT_URL>
|
||||
```
|
||||
|
||||
Where `<YOUR_STOREFRONT_URL>` is the URL of your storefront.
|
||||
|
||||
### 5. Configure Medusa Backend
|
||||
|
||||
Before jumping into the deployment, you must configure your Medusa backend to use the previous environment variables and the recommended production configurations.
|
||||
|
||||
#### medusa-config.js
|
||||
|
||||
Update `module.exports` to include the following configurations:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
projectConfig: {
|
||||
redis_url: REDIS_URL,
|
||||
database_url: DATABASE_URL,
|
||||
database_type: "postgres",
|
||||
store_cors: STORE_CORS,
|
||||
admin_cors: ADMIN_CORS,
|
||||
database_extra:
|
||||
process.env.NODE_ENV !== "development"
|
||||
? { ssl: { rejectUnauthorized: false } }
|
||||
: {},
|
||||
},
|
||||
plugins,
|
||||
modules,
|
||||
}
|
||||
```
|
||||
|
||||
#### package.json
|
||||
|
||||
Update `scripts` to include the following scripts:
|
||||
|
||||
```json
|
||||
"scripts": {
|
||||
"serve": "medusa start",
|
||||
"start": "medusa develop",
|
||||
"heroku-postbuild": "medusa migrations run",
|
||||
"prepare": "npm run build",
|
||||
"build": "babel src -d dist --extensions \".ts,.js\""
|
||||
},
|
||||
```
|
||||
|
||||
### (Optional) 6. Configure the Admin
|
||||
|
||||
If you're using the Medusa Admin plugin, you have two options to deploy it: either with the backend or separately.
|
||||
|
||||
#### Deploying with the Backend
|
||||
|
||||
To deploy the admin with the backend:
|
||||
|
||||
1. Your chosen plan must offer at least 2GB of RAM.
|
||||
2. Enable the [autoRebuild option](../../admin/configuration.mdx#plugin-options) of the admin plugin:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: "@medusajs/admin",
|
||||
/** @type {import('@medusajs/admin').PluginOptions} */
|
||||
options: {
|
||||
autoRebuild: true,
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
Alternatively, you can use a GitHub action to build the admin as explained [here](../index.mdx#deploy-admin-through-github-action).
|
||||
|
||||
#### Deploying Separately
|
||||
|
||||
If you choose to deploy the admin separately, disable the admin plugin's [serve option](../../admin/configuration.mdx#plugin-options):
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: "@medusajs/admin",
|
||||
/** @type {import('@medusajs/admin').PluginOptions} */
|
||||
options: {
|
||||
// only enable `serve` in development
|
||||
// you may need to add the NODE_ENV variable
|
||||
// manually
|
||||
serve: process.env.NODE_ENV === "development",
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
This ensures that the admin isn't built or served in production. You can also change `@medusajs/admin` dependency to be a dev dependency in `package.json`.
|
||||
|
||||
You can alternatively remove the admin plugin for the plugins array.
|
||||
|
||||
:::tip
|
||||
|
||||
Refer to the [admin deployment guides on how to deploy the admin separately](../admin/index.mdx).
|
||||
|
||||
:::
|
||||
|
||||
### 7. Launch your Medusa Backend
|
||||
|
||||
Finally, commit and push all changes to Heroku:
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "Deploy Medusa Backend on Heroku"
|
||||
git push heroku HEAD:master
|
||||
```
|
||||
|
||||
This triggers a redeploy of the Medusa backend with all the new configurations.
|
||||
|
||||
---
|
||||
|
||||
## Test your Backend
|
||||
|
||||
To test your backend, run the following command to retrieve the backend's URL:
|
||||
|
||||
```bash
|
||||
heroku apps:info -a <APP_NAME>
|
||||
```
|
||||
|
||||
Where `<APP_NAME>` is the name of the app. You should see as the output a bunch of info of the app.
|
||||
|
||||
The backend's URL is available under "Web URL". You can copy it and perform requests to it to test it out.
|
||||
|
||||
For example, you can send a request to `<YOUR_BACKEND_URL>/store/products` and you should receive a JSON response with the products in your store.
|
||||
|
||||
### Health Route
|
||||
|
||||
You can access `/health` to get health status of your deployed backend.
|
||||
|
||||
### Testing the Admin
|
||||
|
||||
If you deployed the [admin dashboard with the backend](#deploying-with-the-backend), you can test it by going to `<YOUR_APP_URL>/app`. If you changed the admin path, make sure to change `/app` to the path you've set.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Inspect Build Logs
|
||||
|
||||
If an error occurs during the deployment, you can explore your Heroku app build logs using the following command in the root directory of your Medusa backend:
|
||||
|
||||
```bash
|
||||
heroku logs -n 500000 --remote heroku --tail -a <APP_NAME>
|
||||
```
|
||||
|
||||
Where `<APP_NAME>` is the name of the app.
|
||||
|
||||
### Error: Babel not found
|
||||
|
||||
If you get the following error in the logs of your application:
|
||||
|
||||
```bash
|
||||
/bin/sh: 1: /app/node_modules/.bin/babel: not found
|
||||
```
|
||||
|
||||
You can resolve it by creating a file in the root of your Medusa backend directory named `Procfile` (without an extension) with the following content:
|
||||
|
||||
```
|
||||
web: npm run serve
|
||||
```
|
||||
|
||||
Then, push the changes with Git:
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "Added Procfile"
|
||||
git push heroku HEAD:master
|
||||
```
|
||||
|
||||
Once the application is re-published, you can access your store as expected.
|
||||
|
||||
---
|
||||
|
||||
## Run Commands on Your Backend
|
||||
|
||||
To run commands on your backend, you can use the following command:
|
||||
|
||||
```bash
|
||||
heroku run -a <APP_NAME> -- <COMMAND>
|
||||
```
|
||||
|
||||
Where `<APP_NAME>` is the name of the app and `<COMMAND>` is the command you want to run.
|
||||
|
||||
For example, to create an admin user you can run the following command:
|
||||
|
||||
```bash
|
||||
heroku run -a <APP_NAME> -- npx medusa user -e "<EMAIL>" -p "<PASSWORD>"
|
||||
```
|
||||
|
||||
Where `<APP_NAME>` is the name of your Heroku app, and `<EMAIL>` and `<PASSWORD>` are the credentials you want to use to log in to the Medusa Admin dashboard.
|
||||
|
||||
---
|
||||
|
||||
## Add Environment Variables
|
||||
|
||||
You’ll likely need to add environment variables later such as Admin Cross-Origin Resource Sharing (CORS) and Store CORS variables.
|
||||
|
||||
To set or change an environment variable's value, you can use the following command:
|
||||
|
||||
```bash
|
||||
heroku config:set <ENV_NAME>=<ENV_VALUE> -a <APP_NAME>
|
||||
```
|
||||
|
||||
Where `<APP_NAME>` is the name of your Heroku app, `<ENV_NAME>` is the name of the environment variable, and `<ENV_VALUE>` is the value.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Deploy your Medusa Admin](../admin/index.mdx)
|
||||
- [Deploy your storefront](../storefront/index.mdx)
|
||||
@@ -1,183 +0,0 @@
|
||||
---
|
||||
description: 'Learn step-by-step.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# Deploy Your Medusa Backend on Microtica
|
||||
|
||||
In this document, you'll learn how to deploy your Medusa backend on your AWS account with Microtica.
|
||||
|
||||
:::note
|
||||
|
||||
This guide was submitted through a community contribution.
|
||||
|
||||
:::
|
||||
|
||||
## Overview
|
||||
|
||||
[Microtica](https://microtica.com) is a cloud-native delivery platform that enables you to deploy infrastructure and applications to your AWS account, while providing actionable insights to help you optimize resources, performance, & costs.
|
||||
|
||||
Alternatively, you can use this button to deploy the Medusa backend to Microtica directly:
|
||||
|
||||
<a href="https://app.microtica.com/templates/new?template=https%3A%2F%2Fraw.githubusercontent.com%2Fmicrotica%2Ftemplates%2Fmaster%2Fmedusa-server%2F.microtica%2Ftemplate.yaml&utm_source=medusa&utm_medium=docs&utm_campaign=medusa" className="img-url">
|
||||
<img src="https://microtica.s3.eu-central-1.amazonaws.com/assets/templates/logos/deploy-with-microtica.svg" alt="Deploy with Microtica" className="no-zoom-img" />
|
||||
</a>
|
||||
|
||||
:::note
|
||||
|
||||
The Medusa infrastructure will be provisioned on your own AWS account.
|
||||
You retain full control over your infrastructure and data while getting all the benefits of infrastructure automation.
|
||||
|
||||
:::
|
||||
|
||||
### What will be provisioned on AWS
|
||||
|
||||
Since Microtica deploys on your cloud account, here are the resources that the platform is going to provision in the environment.
|
||||
|
||||
- VPC, subnets and networking
|
||||
- Container infrastructure based on Fargate
|
||||
- application load balancer
|
||||
- persistent storage
|
||||
- S3 bucket
|
||||
- Postgres database
|
||||
- and Redis (in production mode)
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Backend
|
||||
|
||||
It is assumed that you already have a Medusa backend installed locally. If you don’t, please follow the [quickstart guide](../../development/backend/install.mdx).
|
||||
|
||||
Furthermore, your Medusa backend should be configured to work with PostgreSQL and Redis. You can follow the [Configure your Backend documentation](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx) to learn how to do that.
|
||||
|
||||
### Needed Accounts
|
||||
|
||||
- A [Microtica](https://app.microtica.com/) account. Microtica provides a free plan that you can use.
|
||||
- An [AWS](https://aws.amazon.com/) account that you’ll connect to Microtica to provision the infrastructure.
|
||||
- A [GitHub](https://github.com/) account to create a repository to host your backend's codebase.
|
||||
|
||||
### Required Tools
|
||||
|
||||
- Git’s CLI tool. You can follow [this documentation to learn how to install it for your operating system](../../development/backend/prepare-environment.mdx#git).
|
||||
|
||||
---
|
||||
|
||||
## Deploy to Microtica
|
||||
|
||||
### (Optional) Step 0: Configure the Admin
|
||||
|
||||
If you're using the Medusa Admin plugin, you have two options to deploy it: either with the backend or separately.
|
||||
|
||||
#### Deploying with the Backend
|
||||
|
||||
To deploy the admin with the backend:
|
||||
|
||||
1. Your chosen plan must offer at least 2GB of RAM.
|
||||
2. Enable the [autoRebuild option](../../admin/configuration.mdx#plugin-options) of the admin plugin:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: "@medusajs/admin",
|
||||
/** @type {import('@medusajs/admin').PluginOptions} */
|
||||
options: {
|
||||
autoRebuild: true,
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
Alternatively, you can use a GitHub action to build the admin as explained [here](../index.mdx#deploy-admin-through-github-action).
|
||||
|
||||
#### Deploying Separately
|
||||
|
||||
If you choose to deploy the admin separately, disable the admin plugin's [serve option](../../admin/configuration.mdx#plugin-options):
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: "@medusajs/admin",
|
||||
/** @type {import('@medusajs/admin').PluginOptions} */
|
||||
options: {
|
||||
// only enable `serve` in development
|
||||
// you may need to add the NODE_ENV variable
|
||||
// manually
|
||||
serve: process.env.NODE_ENV === "development",
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
This ensures that the admin isn't built or served in production. You can also change `@medusajs/admin` dependency to be a dev dependency in `package.json`.
|
||||
|
||||
You can alternatively remove the admin plugin for the plugins array.
|
||||
|
||||
:::tip
|
||||
|
||||
Refer to the [admin deployment guides on how to deploy the admin separately](../admin/index.mdx).
|
||||
|
||||
:::
|
||||
|
||||
### Step 1: Create/Import a Git repository
|
||||
|
||||
Before you can deploy your Medusa backend you need to connect your preferred Git account. Microtica will create a repository on your Git account with a default repo name `medusa-server`.
|
||||
|
||||
If you already have a Medusa backend repository that you want to deploy then you need to perform a couple of changes in your source code. Follow [this tutorial to deploy and existing Medusa Backend app](https://docs.microtica.com/medusa-server?utm_source=medusa&utm_medium=docs&utm_campaign=medusa#xUBRz).
|
||||
|
||||
### Step 2: Configure the Template
|
||||
|
||||
The second step provides customization possibilities by configuring environment variables.
|
||||
|
||||
Add an application name and the admin credentials that will be used to create an initial admin user with which you can later sign in to your Medusa Admin.
|
||||
|
||||
Then, choose whether you want a production Medusa Backend environment or a development one. The production template will provision your managed Relational Database Service (RDS) PostgreSQL and Redis instances.
|
||||
|
||||
### Step 3: Connect an AWS account
|
||||
|
||||
In the last step before deployment, select the environment in which you want to deploy the template. An existing default environment called `development` will be preselected here, or you can create a new environment.
|
||||
|
||||
Then, connect your AWS account when prompted. This process takes only a few seconds, so afterward only choose the region you want to deploy in.
|
||||
|
||||
### Step 4: Deploy to AWS
|
||||
|
||||
Finally, a deployment summary of what will be provisioned on your AWS account is presented. Click on the "Deploy" button to trigger a deployment of the template and start creating the infrastructure for a Medusa backend.
|
||||
|
||||
It will take around ten minutes for the solution to be deployed on your cloud account.
|
||||
|
||||
You can follow the build pipeline in real-time by clicking the "View Logs" button.
|
||||
|
||||
Once the build process is complete, a new deployment with the infrastructure resources is triggered. You can follow the logs of the deployment process by clicking the "View deployment" button, and then selecting the deployment from the list.
|
||||
|
||||
---
|
||||
|
||||
## Test the Backend
|
||||
|
||||
After the deployment is finished, navigate to Resources → [AppName] (Medusa in this example) → Overview. Then, under the Resource Outputs section you should see the "AccessUrl". This is the backend's URL that you can use to access API Routes and test them. You can try getting the list of products using the API Route `<AccessUrl>/store/products`.
|
||||
|
||||
### Health Route
|
||||
|
||||
You can access `/health` to get health status of your deployed backend.
|
||||
|
||||
### Testing the Admin
|
||||
|
||||
If you deployed the [admin dashboard with the backend](#deploying-with-the-backend), you can test it by going to `<AccessUrl>/app`. If you changed the admin path, make sure to change `/app` to the path you've set.
|
||||
|
||||
---
|
||||
|
||||
## Add Environment Variables
|
||||
|
||||
The environment variables can be updated, added, or configured after deployment as well. You can access them with `process.env.<VARIABLE NAME>`.
|
||||
|
||||
You can read more about the built-in environment variables, as well as how to specify custom environment variables in the Medusa backend runtime [in Microtica documentation](https://docs.microtica.com/medusa-server?utm_source=medusa&utm_medium=docs&utm_campaign=medusa#z8li6).
|
||||
|
||||
---
|
||||
|
||||
## Updating your Deployed Backend
|
||||
|
||||
Any updates to the backend (for example, updating the Medusa core version) are deployed automatically when changes are committed to the repository. Microtica will handle the entire process of building and deploying your application on your connected AWS account.
|
||||
@@ -1,381 +0,0 @@
|
||||
---
|
||||
description: 'Learn step-by-step.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from "@theme/Tabs"
|
||||
import TabItem from "@theme/TabItem"
|
||||
import MimeErrorTroubleshooting from "../../troubleshooting/mime-error.md"
|
||||
|
||||
# Deploy Your Medusa Backend to Railway
|
||||
|
||||
In this document, you’ll learn how to deploy your Medusa backend to Railway.
|
||||
|
||||
## What is Railway
|
||||
|
||||
[Railway](https://railway.app/) is a hosting provider that you can use to deploy web applications and databases without having to worry about managing the full infrastructure.
|
||||
|
||||
Railway provides a free trial (no credit card required) that allows you to deploy your Medusa backend along with PostgreSQL and Redis databases. This is useful mainly for development and demo purposes.
|
||||
|
||||
<a
|
||||
href="https://railway.app/template/zC7eOq?referralCode=TW4Qi0" className="img-url no-zoom-img mb-2 inline-block">
|
||||
<img src="https://railway.app/button.svg" alt="Deploy with Railway" className="no-zoom-img"/>
|
||||
</a>
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- [Medusa backend installed locally](../../create-medusa-app.mdx)
|
||||
- Production modules installed on the Medusa backend, such as [Redis Event Module](../../development/events/modules/redis.md) and [Redis Cache Module](../../development/cache/modules/redis.md).
|
||||
- A [Railway](https://railway.app/) account. If you're deploying the admin plugin along with the backend, you'll need at least the Developer plan. The resources provided by the starter plan lead to memory errors.
|
||||
- [A GitHub repository for your Medusa backend](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-new-repository).
|
||||
|
||||
</Note>
|
||||
|
||||
## Configure the Admin
|
||||
|
||||
You have two options to deploy the Medusa Admin: either with the backend or separately.
|
||||
|
||||
### Deploying with the Backend
|
||||
|
||||
To deploy the admin with the backend:
|
||||
|
||||
1. Your chosen plan must offer at least 2GB of RAM.
|
||||
2. Enable the [autoRebuild option](../../admin/configuration.mdx#plugin-options) of the admin plugin:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: "@medusajs/admin",
|
||||
/** @type {import('@medusajs/admin').PluginOptions} */
|
||||
options: {
|
||||
autoRebuild: true,
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
Alternatively, you can use a GitHub action to build the admin as explained [here](../index.mdx#deploy-admin-through-github-action).
|
||||
|
||||
### Deploying Separately
|
||||
|
||||
If you choose to deploy the admin separately, disable the admin plugin's [serve option](../../admin/configuration.mdx#plugin-options):
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: "@medusajs/admin",
|
||||
/** @type {import('@medusajs/admin').PluginOptions} */
|
||||
options: {
|
||||
// only enable `serve` in development
|
||||
// you may need to add the NODE_ENV variable
|
||||
// manually
|
||||
serve: process.env.NODE_ENV === "development",
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
This ensures that the admin isn't built or served in production. You can also change `@medusajs/admin` dependency to be a dev dependency in `package.json`.
|
||||
|
||||
Also, change the `build` command to remove the command that builds the admin:
|
||||
|
||||
```json
|
||||
"scripts": {
|
||||
// ...
|
||||
"build": "cross-env npm run clean && npm run build:server",
|
||||
}
|
||||
```
|
||||
|
||||
:::tip
|
||||
|
||||
Refer to the [admin deployment guides on how to deploy the admin separately](../admin/index.mdx).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Add Railway Configuration File
|
||||
|
||||
To avoid errors during the installation process, it's recommended to use `yarn` for installing the dependencies. Alternatively, pass the `--legacy-peer-deps` option to the npm command.
|
||||
|
||||
Add in the root of your Medusa project the file `railway.toml` with the content based on the package manager of your choice:
|
||||
|
||||
<Tabs groupId="railway-config" isCodeTabs={true} codeTitle="railway.toml">
|
||||
<TabItem label="yarn" value="yarn">
|
||||
|
||||
```toml
|
||||
[build]
|
||||
builder = "NIXPACKS"
|
||||
|
||||
[build.nixpacksPlan.phases.setup]
|
||||
nixPkgs = ["nodejs", "yarn"]
|
||||
|
||||
[build.nixpacksPlan.phases.install]
|
||||
cmds=["yarn install"]
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="npm" value="npm">
|
||||
|
||||
```toml
|
||||
[build]
|
||||
builder = "NIXPACKS"
|
||||
|
||||
[build.nixpacksPlan.phases.setup]
|
||||
nixPkgs = ["nodejs", "npm"]
|
||||
|
||||
[build.nixpacksPlan.phases.install]
|
||||
cmds=["npm install --legacy-peer-deps"]
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
---
|
||||
|
||||
## Add Worker Mode Configuration
|
||||
|
||||
:::note
|
||||
|
||||
Learn more about the Worker Mode in [this guide](../../development/worker-mode/index.mdx).
|
||||
|
||||
:::
|
||||
|
||||
Set the `worker_mode` configuration in your `medusa-config.js`, if you haven't already:
|
||||
|
||||
```ts
|
||||
const projectConfig = {
|
||||
// ...,
|
||||
database_url: "...",
|
||||
worker_mode: process.env.MEDUSA_WORKER_MODE,
|
||||
}
|
||||
```
|
||||
|
||||
This allows you to switch between modes for different deployed Medusa instances based on the `MEDUSA_WORKER_MODE` environment variable.
|
||||
|
||||
---
|
||||
|
||||
## Push Changes to GitHub Repo
|
||||
|
||||
Before proceeding further, push all changes you've made to your GitHub repository so that it's included in the deployment process.
|
||||
|
||||
---
|
||||
|
||||
## Create Project + PostgreSQL Database
|
||||
|
||||
On the Railway dashboard:
|
||||
|
||||
1. Click on the ”New Project” button.
|
||||
2. Choose from the list the ”Provision PostgreSQL” option.
|
||||
|
||||
A new database will be created and, after a few seconds, you'll be redirected to the project page where you'll see the newly-created database.
|
||||
|
||||
---
|
||||
|
||||
## Create the Redis Database
|
||||
|
||||
In the same project view:
|
||||
|
||||
1. Click on the New button.
|
||||
2. Choose the Database option.
|
||||
3. Choose Add Redis.
|
||||
|
||||
A new Redis database will be added to the project view in a few seconds. Click on it to open the database sidebar.
|
||||
|
||||
---
|
||||
|
||||
## Module Services and Variables
|
||||
|
||||
If you use modules that require setting up other services or environment variables, make sure to add them at this point. This guide doesn't cover configurations specific to a module.
|
||||
|
||||
---
|
||||
|
||||
## Deploy Medusa in Server Mode
|
||||
|
||||
In this section, you'll create a Medusa backend instance running in `server` runtime mode.
|
||||
|
||||
In the same project view:
|
||||
|
||||
1. Click on the New button.
|
||||
2. Choose the ”GitHub Repo” option.
|
||||
3. If you still haven't given GitHub permissions to Railway, choose the ”Configure GitHub App” option to do that.
|
||||
4. Choose the repository from the GitHub Repo dropdown.
|
||||
|
||||
### Configure Backend Environment Variables
|
||||
|
||||
To configure the environment variables of your Medusa backend:
|
||||
|
||||
1. Click on the GitHub repository’s card.
|
||||
2. Choose the Variables tab.
|
||||
3. Add the following environment variables:
|
||||
|
||||
```bash
|
||||
PORT=9000
|
||||
JWT_SECRET=something
|
||||
COOKIE_SECRET=something
|
||||
DATABASE_URL=${{Postgres.DATABASE_URL}}
|
||||
REDIS_URL=${{Redis.REDIS_URL}}
|
||||
MEDUSA_WORKER_MODE=server
|
||||
```
|
||||
|
||||
Notice that the values of `DATABASE_URL` and `REDIS_URL` reference the values from the PostgreSQL and Redis databases you created.
|
||||
|
||||
:::warning
|
||||
|
||||
It’s highly recommended to use strong, randomly generated secrets for `JWT_SECRET` and `COOKIE_SECRET`.
|
||||
|
||||
:::
|
||||
|
||||
Make sure to add any other environment variables that are relevant for you here. For example, you can add environment variables related to Medusa Admin or your modules.
|
||||
|
||||
### Change Backend's Start Command
|
||||
|
||||
The start command is the command used to run the backend. You’ll change it to run any available migrations, then run the Medusa backend. This way if you create your own migrations or update the Medusa backend, it's guaranteed that these migrations run first before the backend starts.
|
||||
|
||||
To change the start command of your Medusa backend:
|
||||
|
||||
1. Click on the GitHub repository’s card.
|
||||
2. Click on the Settings tab and scroll down to the Deploy section.
|
||||
3. Click on the Start Command button.
|
||||
4. Paste the following in the shown input:
|
||||
|
||||
```bash
|
||||
medusa migrations run && medusa start
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deploy Medusa in Worker Mode
|
||||
|
||||
You'll now create another Medusa instance that'll be in `worker` runtime mode.
|
||||
|
||||
In the same project view:
|
||||
|
||||
1. Click on the New button.
|
||||
2. Choose the ”GitHub Repo” option.
|
||||
3. Choose the same repository from the GitHub Repo dropdown.
|
||||
|
||||
### Configure Environment Variables for Worker Mode
|
||||
|
||||
To configure the environment variables of the Medusa instance running in worker mode:
|
||||
|
||||
1. Click on the card of the Medusa instance you just created..
|
||||
2. Choose the Variables tab.
|
||||
3. Add the following environment variables:
|
||||
|
||||
```bash
|
||||
PORT=9000
|
||||
JWT_SECRET=something
|
||||
COOKIE_SECRET=something
|
||||
DATABASE_URL=${{Postgres.DATABASE_URL}}
|
||||
REDIS_URL=${{Redis.REDIS_URL}}
|
||||
MEDUSA_WORKER_MODE=worker
|
||||
```
|
||||
|
||||
Notice that the values of `DATABASE_URL` and `REDIS_URL` reference the values from the PostgreSQL and Redis databases you created.
|
||||
|
||||
:::warning
|
||||
|
||||
It’s highly recommended to use strong, randomly generated secrets for `JWT_SECRET` and `COOKIE_SECRET`.
|
||||
|
||||
:::
|
||||
|
||||
Make sure to add any other environment variables that are relevant for you here.
|
||||
|
||||
### Change Worker's Start Command
|
||||
|
||||
The start command is the command used to run the Medusa instance in worker mode. To set it:
|
||||
|
||||
1. Click on the worker’s card.
|
||||
2. Click on the Settings tab and scroll down to the Deploy section.
|
||||
3. Click on the Start Command button.
|
||||
4. Paste the following in the shown input:
|
||||
|
||||
```bash
|
||||
medusa start
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Add Domain Name
|
||||
|
||||
The last step is to add a domain name to your Medusa backend. To do that:
|
||||
|
||||
1. Click on the Medusa server runtime's card.
|
||||
2. Click on the Settings tab and scroll down to the Environment section.
|
||||
3. Either click on the Custom Domain button to enter your own domain or the Generate Domain button to generate a random domain.
|
||||
|
||||
---
|
||||
|
||||
## Deploy Changes
|
||||
|
||||
At the top center of your project's dashboard page, there's a Deploy button that deploys all the changes you've made so far.
|
||||
|
||||
Click on the button to trigger the deployment. The deployment will take a few minutes before it's ready.
|
||||
|
||||
---
|
||||
|
||||
## Test the Backend
|
||||
|
||||
Once the deployment is finished, you can access the Medusa backend on the custom domain/domain you've generated.
|
||||
|
||||
For example, you can open the URL `<YOUR_APP_URL>/store/products` which returns the products available on your backend.
|
||||
|
||||
<Note>
|
||||
|
||||
Even if the deployment is successful, you might see a "Railway Not Found" error when accessing your URL. Give it a couple of minutes for the changes to actually take effect and try again.
|
||||
|
||||
</Note>
|
||||
|
||||
### Health Route
|
||||
|
||||
Access `<YOUR_APP_URL>/health` to get health status of your deployed backend.
|
||||
|
||||
### Testing the Admin
|
||||
|
||||
If you deployed the [admin dashboard with the backend](#deploying-with-the-backend), you can test it by going to `<YOUR_APP_URL>/app`. If you changed the admin path, make sure to change `/app` to the path you've set.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you run into any issues or a problem with your deployed backend, you can check the logs in your Railway container instance by:
|
||||
|
||||
1. Click on the GitHub repository’s card.
|
||||
2. Click on the Deployments tab.
|
||||
3. Click on the View Logs button.
|
||||
|
||||
### Error: connect ENOENT
|
||||
|
||||
This error may be thrown by a module that uses Redis. If you see it in your build or deploy logs, make sure that your Redis configurations are correct.
|
||||
|
||||
### Mime Error
|
||||
|
||||
<MimeErrorTroubleshooting />
|
||||
|
||||
---
|
||||
|
||||
## Run Commands on the Deployed Services
|
||||
|
||||
[Railway’s CLI tool](https://docs.railway.app/develop/cli#local-shell) allows you to run commands locally, but using environment variables from your projects.
|
||||
|
||||
For example, you can use it to run Medusa commands such as running migrations or creating a user, and it'll use the database environment variables from Railway to perform the action on the production database.
|
||||
|
||||
### Create Admin User
|
||||
|
||||
To create an admin user in your deployed backend, run the following command in the root of your Medusa project:
|
||||
|
||||
```bash
|
||||
railway run npx medusa user --email admin@medusa-test.com --password supersecret
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Deploy the Medusa Admin](../admin/index.mdx)
|
||||
- [Deploy the Storefront](../storefront/index.mdx)
|
||||
@@ -1,171 +0,0 @@
|
||||
---
|
||||
description: "General steps for deploying the backend."
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# General Deployment Guide for Medusa Backend
|
||||
|
||||
In this guide, you’ll learn the general steps you need to take when deploying your Medusa backend. This is useful when the [platform-specific deployment guides](./index.mdx) do not include your selected hosting provider.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
It’s assumed you already have a Medusa backend installed and configured on your local machine. If not, check out the [create-medusa-app guide](../../create-medusa-app.mdx) to install a Medusa project.
|
||||
|
||||
---
|
||||
|
||||
## (Optional) Step 1: Create GitHub Repository
|
||||
|
||||
Many hosting providers allow you to deploy your project directly from GitHub. This makes it easier for you to push changes and updates without having to manually trigger the update in the hosting provider.
|
||||
|
||||
If your hosting provider supports that, create a GitHub repository and push your backend’s code to it.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Start Script in package.json
|
||||
|
||||
Make sure the `start` script in your `package.json` runs migrations, the `build` command, and the `medusa start` command:
|
||||
|
||||
```json title="package.json"
|
||||
"start": "npm run build && medusa migrations run && medusa start"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Set ssl Database Option
|
||||
|
||||
In production, it’s recommended to set the [database_extra option](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx#database_extra) in `medusa-config.js` to disable the `ssl.rejectUnauthorized` option:
|
||||
|
||||
```jsx title="medusa-config.js"
|
||||
module.exports = {
|
||||
projectConfig: {
|
||||
// ...
|
||||
database_extra: process.env.NODE_ENV !== "development" ?
|
||||
{
|
||||
ssl: {
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
} : {},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Setup PostgreSQL Database
|
||||
|
||||
Your Medusa backend must connect to a remote PostgreSQL database. If your hosting provider doesn’t support creating a PostgreSQL database, you can use [Neon](https://neon.tech/).
|
||||
|
||||
Once you set up your PostgreSQL database, make sure to have the connection URL to the database at hand so that you can set it later in your environment variables.
|
||||
|
||||
---
|
||||
|
||||
## (Optional) Step 5: Configure the Admin
|
||||
|
||||
If you're using the Medusa Admin plugin, you have two options to deploy it: either with the backend or separately.
|
||||
|
||||
### Deploying with the Backend
|
||||
|
||||
To deploy the admin with the backend:
|
||||
|
||||
1. Your chosen hosting provider and plan must offer at least 2GB of RAM.
|
||||
2. Enable the [autoRebuild option](../../admin/configuration.mdx#plugin-options) of the admin plugin:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: "@medusajs/admin",
|
||||
/** @type {import('@medusajs/admin').PluginOptions} */
|
||||
options: {
|
||||
autoRebuild: true,
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
Alternatively, you can use a GitHub action to build the admin as explained [here](../index.mdx#deploy-admin-through-github-action).
|
||||
|
||||
### Deploying Separately
|
||||
|
||||
If you choose to deploy the admin separately, disable the admin plugin's [serve option](../../admin/configuration.mdx#plugin-options):
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: "@medusajs/admin",
|
||||
/** @type {import('@medusajs/admin').PluginOptions} */
|
||||
options: {
|
||||
// only enable `serve` in development
|
||||
// you may need to add the NODE_ENV variable
|
||||
// manually
|
||||
serve: process.env.NODE_ENV === "development",
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
This ensures that the admin isn't built or served in production. You can also change `@medusajs/admin` dependency to be a dev dependency in `package.json`.
|
||||
|
||||
You can alternatively remove the admin plugin for the plugins array.
|
||||
|
||||
:::tip
|
||||
|
||||
Refer to the [admin deployment guides on how to deploy the admin separately](../admin/index.mdx).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## (Optional) Step 6: Setup Architectural Services
|
||||
|
||||
Aside from PostgreSQL, you may be using modules or plugins that require some additional architectural setup. For example, if you’re using the [Redis Events Module](../../development/events/modules/redis.md), you must set up a Redis database and obtain a connection URL to it.
|
||||
|
||||
---
|
||||
|
||||
## Step 7: Deploy your Backend
|
||||
|
||||
You can deploy your backend now to your hosting provider. During or after the deployment process, based on your hosting provider, you need to add the following environment variables:
|
||||
|
||||
```bash
|
||||
DATABASE_TYPE=postgres
|
||||
DATABASE_URL=<YOUR_DB_URL>
|
||||
JWT_SECRET=<RANDOM_SECRET>
|
||||
COOKIE_SECRET=<RANDOM_SECRET>
|
||||
NODE_ENV=production
|
||||
NPM_CONFIG_PRODUCTION=false
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `<YOUR_DB_URL>` is the connection URL to the PostgreSQL database you set up in [step 4](#step-4-setup-postgresql-database).
|
||||
- `<RANDOM_SECRET>` is a random string that will be used to create authentication and cookie tokens. Make sure to set different ones for `JWT_SECRET` and `COOKIE_SECRET`.
|
||||
|
||||
Make sure to also add any other environment variables relevant to your backend. For example, if you’ve setup Redis as explained in [step 5](#optional-step-5-setup-architectural-services), make sure to add an environment variable for the Redis connection URL.
|
||||
|
||||
---
|
||||
|
||||
## Step 8: Test it Out
|
||||
|
||||
After you’ve deployed your backend, you can test it out in different ways:
|
||||
|
||||
- Go to `<BACKEND_URL>/health`, where `<BACKEND_URL>` is the URL to your deployed backend. If the deployment was successful, you should see `OK` printed in your browser.
|
||||
- If you deployed the [admin dashboard with the backend](#deploying-with-the-backend), you can go to `<BACKEND_URL>/app` to view the admin dashboard. If you changed the value of the admin plugin’s `path` configuration, make sure to replace `app` with that instead.
|
||||
|
||||
---
|
||||
|
||||
## Set Up CORS Configuration
|
||||
|
||||
To connect your storefront and, if deployed separately, your admin dashboard to your deployed Medusa backend, make sure to set up the [admin_cors and store_cors configuration](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx#admin_cors) in `medusa-config.js` accordingly.
|
||||
|
||||
---
|
||||
|
||||
## Create Admin User
|
||||
|
||||
If your hosting provider gives you access to execute commands in your deployed Medusa backend project, you can create a new admin user by running the following command in the root directory of your deployed Medusa backend:
|
||||
|
||||
```bash
|
||||
npx medusa user --email admin@medusa-test.com --password supersecret
|
||||
```
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
hide_table_of_contents: true
|
||||
description: 'Learn how to deploy your Medusa backend to different hosting providers including DigitalOcean, Heroku, Railway, and more.'
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
|
||||
# Medusa Backend Deployment Guides
|
||||
|
||||
Find in this page guides to deploy your Medusa backend on different platforms.
|
||||
|
||||
<DocCardList />
|
||||
@@ -1,183 +0,0 @@
|
||||
---
|
||||
description: 'Learn step-by-step.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import DetailsList from '@site/src/components/DetailsList'
|
||||
import CorsErrorSection from '../../troubleshooting/cors-issues.md'
|
||||
|
||||
# Deploy Next.js Starter Template on Vercel
|
||||
|
||||
In this document, you’ll learn how to deploy the Next.js Starter Template on Vercel.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Components
|
||||
|
||||
It is assumed you already have installed the Next.js Starter Template locally. If not, please follow along with [this guide](../../starters/nextjs-medusa-starter.mdx) instead.
|
||||
|
||||
It’s also assumed you already have the Medusa backend deployed, which the Next.js Starter Template interacts with. If not, you can check out one of the [deployment documentation related to the Medusa backend](../server/index.mdx).
|
||||
|
||||
### Storefront Preparations
|
||||
|
||||
If you're not using the [Serverless Modules](../../starters/nextjs-medusa-starter.mdx#using-serverless-modules) in your storefront, either delete the `src/app/api` directory or exclude it in `.gitignore`. Otherwise, the deployment will fail.
|
||||
|
||||
### Required Accounts
|
||||
|
||||
- [Vercel Account](https://vercel.com)
|
||||
- [GitHub Account](https://github.com/): Only required if you’re deploying through the Vercel website.
|
||||
|
||||
:::note
|
||||
|
||||
If you want to use another Git Provider, it’s possible to follow along with this guide, but you’ll have to perform the equivalent steps in your Git Provider.
|
||||
|
||||
:::
|
||||
|
||||
### Required Tools
|
||||
|
||||
- [Git CLI](../../development/backend/prepare-environment.mdx): Only required if you’re deploying through the Vercel website.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create GitHub Repository
|
||||
|
||||
:::note
|
||||
|
||||
This step is only required if you’re deploying from the Vercel website. However, it’s highly recommended to connect your Vercel project to a Git repository for a better developer experience.
|
||||
|
||||
:::
|
||||
|
||||
Before you can deploy your Next.js Starter Template, you need to create a GitHub repository and push the code base to it. To do that:
|
||||
|
||||
1. On GitHub, click the plus icon at the top right, then click New Repository.
|
||||
2. You’ll then be redirected to a new page with a form. In the form, enter the Repository Name.
|
||||
3. Scroll down and click Create repository.
|
||||
|
||||
### Push Code to GitHub Repository
|
||||
|
||||
The next step is to push the code to the GitHub repository you just created.
|
||||
|
||||
After creating the repository, you’ll be redirected to the repository’s page. On that page, you should see a URL that you can copy to connect your repository to a local directory.
|
||||
|
||||
Copy the link. Then, open your terminal in the directory that holds your Next.js Starter Template codebase and run the following commands:
|
||||
|
||||
```bash
|
||||
git init
|
||||
git remote add origin <GITHUB_URL>
|
||||
```
|
||||
|
||||
Where `<GITHUB_URL>` is the URL you just copied.
|
||||
|
||||
Then, add, commit, and push the changes into the repository:
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "initial commit"
|
||||
git push origin master
|
||||
```
|
||||
|
||||
After pushing the changes, you can find the files in your GitHub repository.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Deploy to Vercel
|
||||
|
||||
This section covers how to deploy the storefront, either using the Vercel website or using Vercel’s CLI tool.
|
||||
|
||||
### Option 1: Using the Vercel Website
|
||||
|
||||
This section explains how to deploy the storefront using the Vercel website:
|
||||
|
||||
1. Open the [Vercel dashboard](https://vercel.com/dashboard) after logging in.
|
||||
2. Click on the “Add New…” button next to the search bar.
|
||||
3. Choose Project from the dropdown.
|
||||
4. In the new page that opens, find the Git repository that holds your Next.js Starter Template and click on the Import button. If you haven’t connected your Vercel account to any Git provider, you must do that first.
|
||||
5. In the Configure Project form:
|
||||
1. Open the Environment Variables collapsible, and add the following environment variables:
|
||||
- `NEXT_PUBLIC_MEDUSA_BACKEND_URL`: the URL to your deployed Medusa Backend.
|
||||
- `NEXT_PUBLIC_BASE_URL`: The URL of your storefront. If you don't have a domain ready yet, you can add it later. However, you may face some errors in your storefront.
|
||||
- `REVALIDATE_SECRET`: A secret used for revalidating data. Learn more in the [Next.js documentation](https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#on-demand-revalidation).
|
||||
- If you're using Serverless modules, set the `POSTGRES_URL` environment variable to the connection URL of your PostgreSQL database, and set the `MEDUSA_FF_MEDUSA_V2=true` variable.
|
||||
- You can set any other environment variables you're using at this step or later.
|
||||
2. You can optionally edit the Project Name.
|
||||
6. Once you’re done, click on the “Deploy” button.
|
||||
|
||||
This will start the deployment of the storefront. Once it’s done, you’ll be redirected to the main dashboard of your new project.
|
||||
|
||||
:::note
|
||||
|
||||
At this point, when you visit the storefront, you will face errors related to Cross-Origin Resource Sharing (CORS) while using the storefront. Before you start using the storefront, follow along the [Configure CORS on the Medusa Backend](#step-3-configure-cors-on-the-medusa-backend) section.
|
||||
|
||||
:::
|
||||
|
||||
### Option 2: Using Vercel’s CLI Tool
|
||||
|
||||
This section explains how to deploy the storefront using the Vercel CLI tool. You should have the CLI tool installed first, as explained in [Vercel’s documentation](https://vercel.com/docs/cli).
|
||||
|
||||
In the directory holding your storefront, run the following command to deploy your storefront:
|
||||
|
||||
```bash
|
||||
vercel --build-env NEXT_PUBLIC_MEDUSA_BACKEND_URL=<YOUR_BACKEND_URL>
|
||||
vercel --build-env NEXT_PUBLIC_BASE_URL=<YOUR_BASE_URL>
|
||||
vercel --build-env REVALIDATE_SECRET=<YOUR_REVALIDATION_SECRET>
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `<YOUR_BACKEND_URL>`: the URL of your deployed Medusa backend.
|
||||
- `<YOUR_BASE_URL>`: The URL of your storefront. If you don't have a domain ready yet, you can add it later. However, you may face some errors in your storefront.
|
||||
- `<YOUR_REVALIDATION_SECRET>`: A secret used for revalidating data. Learn more in the [Next.js documentation](https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#on-demand-revalidation).
|
||||
|
||||
In addition:
|
||||
|
||||
- If you're using Serverless modules, set the `POSTGRES_URL` environment variable to the connection URL of your PostgreSQL database, and set the `MEDUSA_FF_MEDUSA_V2=true` variable.
|
||||
- You can set any other environment variables you're using at this step or later.
|
||||
|
||||
You’ll then be asked to log in if you haven’t already, and to choose the scope to deploy your project to. You can also decide to link the storefront to an existing project, or change the project’s name.
|
||||
|
||||
When asked `In which directory is your code located?`, keep the default `./` and just press Enter.
|
||||
|
||||
The project setup will then start. When asked if you want to modify the settings, answer `N` to keep the default settings.
|
||||
|
||||
It will take a couple of minutes for the deployment to finish. The link to the storefront will be shown in the final output of the command.
|
||||
|
||||
:::note
|
||||
|
||||
At this point, when you visit the storefront, you will face errors related to Cross-Origin Resource Sharing (CORS) while using the storefront. Before you start using the storefront, follow along the [Configure CORS on the Medusa Backend](#step-3-configure-cors-on-the-medusa-backend) section.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Configure CORS on the Medusa Backend
|
||||
|
||||
To send requests to the Medusa backend from the Next.js Starter Template, you must set the `STORE_CORS` environment variable on your backend to the Next.js Starter Template’s URL.
|
||||
|
||||
:::tip
|
||||
|
||||
If you want to set a custom domain to your Next.js Starter Template website on Vercel, make sure to do it before this step. You can refer to this guide on [Vercel’s documentation](https://vercel.com/docs/concepts/projects/domains/add-a-domain).
|
||||
|
||||
:::
|
||||
|
||||
On your Medusa backend, add the following environment variable:
|
||||
|
||||
```bash
|
||||
STORE_CORS=<STOREFRONT_URL>
|
||||
```
|
||||
|
||||
Where `<STOREFRONT_URL>` is the URL of your Next.js Starter Template that you just deployed.
|
||||
|
||||
Then, restart your Medusa backend. Once the backend is running again, you can use your Next.js Starter Template.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<DetailsList
|
||||
sections={[
|
||||
{
|
||||
title: 'CORS Error',
|
||||
content: <CorsErrorSection />
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,96 +0,0 @@
|
||||
---
|
||||
description: "General steps for deploying the Next.js starter."
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import DetailsList from '@site/src/components/DetailsList'
|
||||
import CorsErrorSection from '../../troubleshooting/cors-issues.md'
|
||||
|
||||
# General Deployment Guide for Next.js Starter
|
||||
|
||||
In this guide, you’ll learn the general steps to follow when deploying the Next.js starter storefront. This guide isn’t tailored towards any hosting provider.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Deployed Medusa Backend
|
||||
|
||||
The deployed storefront must connect to a publicly deployed backend. So, deploy the backend first to obtain its URL.
|
||||
|
||||
You can follow one of [these backend deployment guides](../server/index.mdx).
|
||||
|
||||
### Installed Storefront
|
||||
|
||||
You must have an installed Next.js storefront before proceeding with the deployment. You can refer to [this installation guide](../../starters/nextjs-medusa-starter.mdx).
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Storefront Preparations
|
||||
|
||||
If you're not using the [Serverless Modules](../../starters/nextjs-medusa-starter.mdx#using-serverless-modules) in your storefront, either delete the `src/app/api` directory or exclude it in `.gitignore`.
|
||||
|
||||
Otherwise, the deployment may fail.
|
||||
|
||||
---
|
||||
|
||||
## (Optional) Step 2: Create GitHub Repository
|
||||
|
||||
Many hosting providers allow you to deploy your project directly from GitHub. This makes it easier for you to push changes and updates without having to manually trigger the update in the hosting provider.
|
||||
|
||||
If your hosting provider supports that, create a GitHub repository and push the Next.js starter’s code:
|
||||
|
||||
```bash
|
||||
git init
|
||||
git remote add origin <GITHUB_URL>
|
||||
git add .
|
||||
git commit -m "initial commit"
|
||||
git push
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Deploy to Hosting Provider
|
||||
|
||||
The steps to deploy the Next.js starter can be different based on the hosting provider you use. The following points cover common configurations across hosting providers:
|
||||
|
||||
- If your hosting provider supports choosing a framework preset, set it to Next.js. You can then skip setting the build command and output directory as they are default Next.js configurations.
|
||||
- Set the build command of your deployed project to the `build` command:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
- Set the output directory of your deployed project to `.next`.
|
||||
- Add the following environment variables:
|
||||
- `NEXT_PUBLIC_MEDUSA_BACKEND_URL`: The URL of your deployed Medusa backend.
|
||||
- `NEXT_PUBLIC_BASE_URL`: The URL of the storefront. If you still don’t have the URL you can wait until later to add it. However, you may face errors when using the storefront.
|
||||
- `REVALIDATE_SECRET`: A secret used for revalidating data. Learn more in the [Next.js documentation](https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#on-demand-revalidation).
|
||||
- If you’re using serverless modules:
|
||||
- Set `MEDUSA_FF_MEDUSA_V2` to `true`.
|
||||
- Set `POSTGRES_URL` to the connection URL of the PostgreSQL database.
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Configure CORS on the Deployed Backend
|
||||
|
||||
To send requests from the Next.js starter storefront to the Medusa backend, you must set the `STORE_CORS` environment variable on your backend to the storefront’s URL:
|
||||
|
||||
```bash
|
||||
STORE_CORS=<STORE_URL>
|
||||
```
|
||||
|
||||
Where `<STORE_URL>` is the URL of your Next.js starter storefront that you just deployed.
|
||||
|
||||
Then, restart your Medusa backend. Once the backend is running again, you can use your storefront.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<DetailsList
|
||||
sections={[
|
||||
{
|
||||
title: 'CORS Error',
|
||||
content: <CorsErrorSection />
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
hide_table_of_contents: true
|
||||
description: 'Learn how to deploy your storefronts to different hosting providers to be used with a deployed Medusa backend.'
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
|
||||
# Storefront Deployment Guides
|
||||
|
||||
Find in this page guides to deploy your storefront on different platforms.
|
||||
|
||||
<DocCardList />
|
||||
@@ -1,221 +0,0 @@
|
||||
---
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import DetailsList from '@site/src/components/DetailsList'
|
||||
import ServiceLifetimeSection from '../../troubleshooting/awilix-resolution-error/_service-lifetime.md'
|
||||
import CustomRegistrationSection from '../../troubleshooting/awilix-resolution-error/_custom-registration.md'
|
||||
|
||||
# Middlewares - Express Endpoints Approach
|
||||
|
||||
In this document, you’ll learn how to add a middleware to an existing or custom route in Medusa.
|
||||
|
||||
:::note
|
||||
|
||||
Following v1.17.2 of `@medusajs/medusa`, it's highly recommended to use the [middlewares.ts file](./add-middleware.mdx) to create middlewares instead. Future versions of Medusa may drop support of this approach.
|
||||
|
||||
:::
|
||||
|
||||
## Overview
|
||||
|
||||
As the Medusa backend is built on top of [Express](https://expressjs.com/), Express’s features can be utilized during your development with Medusa.
|
||||
|
||||
One feature in particular is adding a [middleware](http://expressjs.com/en/guide/using-middleware.html#using-middleware). A middleware is a function that has access to the request and response objects.
|
||||
|
||||
A middleware can be used to perform an action when an endpoint is called or modify the response, among other usages.
|
||||
|
||||
You can add a middleware to an existing route in the Medusa backend, a route in a plugin, or your custom routes.
|
||||
|
||||
---
|
||||
|
||||
## How to Add a Middleware
|
||||
|
||||
### Step 1: Create the Middleware File
|
||||
|
||||
You can organize your middlewares as you see fit, but it's recommended to create Middlewares in the `src/api/middlewares` directory. It's recommended to create each middleware in a different file.
|
||||
|
||||
Each file should export a middleware function that accepts three parameters:
|
||||
|
||||
1. The first one is an Express request object. It can be used to get details related to the request or resolve resources from the dependency container.
|
||||
2. The second one is an Express response object. It can be used to modify the response, or in some cases return a response without executing the associated endpoint.
|
||||
3. The third one is a next middleware function that ensures that other middlewares and the associated endpoint are executed.
|
||||
|
||||
:::info
|
||||
|
||||
You can learn more about Middlewares and their capabilities in [Express’s documentation](http://expressjs.com/en/guide/using-middleware.html#using-middleware).
|
||||
|
||||
:::
|
||||
|
||||
Here's an example of a middleware:
|
||||
|
||||
```ts title="src/api/middlewares/custom-middleware.ts"
|
||||
export function customMiddleware(req, res, next) {
|
||||
// TODO perform an action
|
||||
|
||||
next()
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Apply Middleware on an Endpoint
|
||||
|
||||
To apply a middleware on any endpoint, you can use the same router defined in `src/api/index.ts` or any other router that is used or exported by `src/api/index.ts`. For example:
|
||||
|
||||
:::warning
|
||||
|
||||
The examples used here don't apply Cross-Origin Resource Origin (CORS) options for simplicity. Make sure to apply them, especially for core routes, as explained in the [Create Endpoint](./create.mdx#cors-configuration) documentation.
|
||||
|
||||
:::
|
||||
|
||||
```ts title="src/api/index.ts"
|
||||
import { Router } from "express"
|
||||
import {
|
||||
customMiddleware,
|
||||
} from "./middlewares/custom-middleware"
|
||||
|
||||
export default (rootDirectory, pluginOptions) => {
|
||||
const router = Router()
|
||||
|
||||
// custom route
|
||||
router.get("/hello", (req, res) => {
|
||||
res.json({
|
||||
message: "Welcome to My Store!",
|
||||
})
|
||||
})
|
||||
|
||||
// middleware for the custom route
|
||||
router.use("/hello", customMiddleware)
|
||||
|
||||
// middleware for core route
|
||||
router.use("/store/products", customMiddleware)
|
||||
|
||||
return router
|
||||
}
|
||||
```
|
||||
|
||||
## Step 3: Building Files
|
||||
|
||||
Similar to custom endpoints, you must transpile the files under `src` into the `dist` directory for the backend to load them.
|
||||
|
||||
To do that, run the following command before running the Medusa backend:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can then test that the middleware is working by running the backend.
|
||||
|
||||
---
|
||||
|
||||
## Registering New Resources in Dependency Container
|
||||
|
||||
In some cases, you may need to register a resource to use within your commerce application. For example, you may want to register the logged-in user to access it in other services. You can do that in your middleware.
|
||||
|
||||
:::tip
|
||||
|
||||
If you want to register a logged-in user and access it in your resources, you can check out [this example guide](./example-logged-in-user.mdx).
|
||||
|
||||
:::
|
||||
|
||||
To register a new resource in the dependency container, use the `req.scope.register` method:
|
||||
|
||||
```ts title="src/api/middlewares/custom-middleware.ts"
|
||||
export function customMiddleware(req, res, next) {
|
||||
// TODO perform an action
|
||||
|
||||
req.scope.register({
|
||||
customResource: {
|
||||
resolve: () => "my custom resource",
|
||||
},
|
||||
})
|
||||
|
||||
next()
|
||||
}
|
||||
```
|
||||
|
||||
You can then load this new resource within other resources. For example, to load it in a service:
|
||||
|
||||
<!-- eslint-disable prefer-rest-params -->
|
||||
|
||||
```ts title="src/services/custom-service.ts"
|
||||
import { TransactionBaseService } from "@medusajs/medusa"
|
||||
|
||||
class CustomService extends TransactionBaseService {
|
||||
|
||||
constructor(container, options) {
|
||||
super(...arguments)
|
||||
|
||||
// use the registered resource.
|
||||
try {
|
||||
container.customResource
|
||||
} catch (e) {
|
||||
// avoid errors when the backend first loads
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default CustomService
|
||||
```
|
||||
|
||||
Notice that you have to wrap your usage of the new resource in a try-catch block when you use it in a constructor. This is to avoid errors that can arise when the backend first loads, as the resource is not registered yet.
|
||||
|
||||
### Note About Services Lifetime
|
||||
|
||||
If you want to access new registrations in the dependency container within a service, you must set the lifetime of the service either to `Lifetime.SCOPED` or `Lifetime.TRANSIENT`. Services that have a `Lifetime.SINGLETON` lifetime can't access new registrations since they're resolved and cached in the root dependency container beforehand. You can learn more in the [Create Services documentation](../services/create-service.mdx#service-life-time).
|
||||
|
||||
For custom services, no additional action is required as the default lifetime is `Lifetime.SCOPED`. However, if you extend a core service, you must change the lifetime since the default lifetime for core services is `Lifetime.SINGLETON`.
|
||||
|
||||
For example:
|
||||
|
||||
<!-- eslint-disable prefer-rest-params -->
|
||||
|
||||
```ts
|
||||
import { Lifetime } from "awilix"
|
||||
import {
|
||||
ProductService as MedusaProductService,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
// extending ProductService from the core
|
||||
class ProductService extends MedusaProductService {
|
||||
// The default life time for a core service is SINGLETON
|
||||
static LIFE_TIME = Lifetime.SCOPED
|
||||
|
||||
constructor(container, options) {
|
||||
super(...arguments)
|
||||
|
||||
// use the registered resource.
|
||||
try {
|
||||
container.customResource
|
||||
} catch (e) {
|
||||
// avoid errors when the backend first loads
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default ProductService
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<DetailsList
|
||||
sections={[
|
||||
{
|
||||
title: 'AwilixResolutionError: Could Not Resolve X',
|
||||
content: <ServiceLifetimeSection />
|
||||
},
|
||||
{
|
||||
title: 'AwilixResolutionError: Could Not Resolve X (Custom Registration)',
|
||||
content: <CustomRegistrationSection />
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Store API reference](https://docs.medusajs.com/api/store)
|
||||
- [Admin API reference](https://docs.medusajs.com/api/admin)
|
||||
@@ -1,223 +0,0 @@
|
||||
---
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import DetailsList from '@site/src/components/DetailsList'
|
||||
import ServiceLifetimeSection from '../../troubleshooting/awilix-resolution-error/_service-lifetime.md'
|
||||
import CustomRegistrationSection from '../../troubleshooting/awilix-resolution-error/_custom-registration.md'
|
||||
|
||||
# Middlewares
|
||||
|
||||
In this document, you’ll learn how to add a middleware to existing or custom API Routes in Medusa.
|
||||
|
||||
:::tip
|
||||
|
||||
v1.17.2 of `@medusajs/medusa` introduced a new approach to creating middlewares using a single `middlewares.ts` file. You can still use the [Express Router Approach](./add-middleware-express-route.mdx), however, it's highly recommended that you start using this new approach.
|
||||
|
||||
:::
|
||||
|
||||
## Basic Implementation
|
||||
|
||||
```ts title="src/api/middlewares.ts"
|
||||
import type { MiddlewaresConfig } from "@medusajs/medusa"
|
||||
import type {
|
||||
MedusaNextFunction,
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
const storeMiddleware = (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse,
|
||||
next: MedusaNextFunction
|
||||
) => {
|
||||
// do something
|
||||
next()
|
||||
}
|
||||
|
||||
export const config: MiddlewaresConfig = {
|
||||
routes: [
|
||||
{
|
||||
matcher: "/store/*",
|
||||
middlewares: [storeMiddleware],
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
A middleware is a function that has access to the `MedusaRequest` and `MedusaResponse` objects that are passed to API Route method handlers.
|
||||
|
||||
Middlewares are used to perform an action when a request is sent to an API Route, or modify the response of an API route, among other usages.
|
||||
|
||||
### middlewares.ts File
|
||||
|
||||
Middlewares are defined in the `src/api/middlewares.ts` file. This file must expose a config object of type `MiddlewaresConfig` imported from `@medusajs/medusa`.
|
||||
|
||||
This object accepts two parameters:
|
||||
|
||||
- `routes`: an array of middleware route objects. Each middleware route object accepts the following properties:
|
||||
- `matcher`: a string or a regular expression that will be used to check whether the middlewares should be applied on a API Routes.
|
||||
- `middlewares`: an array of middlewares that should be applied on API Routes matching the pattern specified in `matcher`.
|
||||
- `method`: a string or an array of strings, where each value is the name of an HTTP method. This allows you to apply the middlewares only on the specified HTTP methods in this property. If not specified, the middlewares are applied on all HTTP methods.
|
||||
- `bodyParser`: This property can have two types of values:
|
||||
- `false`: If this value is used, the default `json` body parser middleware is disabled. This allows you to override the body parser with your own middleware. For example, it's useful when changing the [default parser for webhooks](./create.mdx#parse-request-body-parameters).
|
||||
- `object`: You can pass an object with configurations for the default `json` body parser middleware. It accepts the following configurations:
|
||||
- `sizeLimit`: A number indicating the maximum body size allowed in bytes. By default, it's `1000` bytes. If a request body is greater than that, an error will be thrown. So, you can set this configuration if you expect the request body size of an API route to be greater than `1000` bytes.
|
||||
- `errorHandler`: This property can have two types of values:
|
||||
- `false`: If this value is used, the default error handler is disabled. Learn more about its behavior [here](./create.mdx#disable-default-error-handler).
|
||||
- `function`: A middleware function used to define a new error handler. Learn more [here](./create.mdx#override-error-handler).
|
||||
|
||||
---
|
||||
|
||||
## Build Files
|
||||
|
||||
Similar to custom API Routes, you must transpile the files under `src` into the `dist` directory for the backend to load them.
|
||||
|
||||
To do that, run the following command before running the Medusa backend:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can then test that the middleware is working by running the backend.
|
||||
|
||||
---
|
||||
|
||||
## Register New Resources in Dependency Container
|
||||
|
||||
In some cases, you may need to register a resource to use within your commerce application. For example, you may want to register the logged-in user to access it in other resources, such as services. You can do that in your middleware.
|
||||
|
||||
:::tip
|
||||
|
||||
If you want to register a logged-in user and access it in your resources, you can check out [this example guide](./example-logged-in-user.mdx).
|
||||
|
||||
:::
|
||||
|
||||
To register a new resource in the dependency container, use the `MedusaRequest` object's `scope.register` method. It accepts an object, where each key is the name to be registered in the dependency container, and its value is an object that has a `resolve` property.
|
||||
|
||||
The `resolve`'s value is a function that returns the resource to be registered in the dependency container.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/middlewares.ts"
|
||||
import type { MiddlewaresConfig } from "@medusajs/medusa"
|
||||
import type {
|
||||
MedusaNextFunction,
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
const customResource = (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse,
|
||||
next: MedusaNextFunction) => {
|
||||
req.scope.register({
|
||||
customResource: {
|
||||
resolve: () => "my custom resource",
|
||||
},
|
||||
})
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
export const config: MiddlewaresConfig = {
|
||||
routes: [
|
||||
{
|
||||
matcher: "/store/*",
|
||||
middlewares: [customResource],
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
You can then load this new resource within other resources. For example, to load it in a service:
|
||||
|
||||
<!-- eslint-disable prefer-rest-params -->
|
||||
|
||||
```ts title="src/services/custom-service.ts"
|
||||
import { TransactionBaseService } from "@medusajs/medusa"
|
||||
|
||||
class CustomService extends TransactionBaseService {
|
||||
|
||||
constructor(container, options) {
|
||||
super(...arguments)
|
||||
|
||||
// use the registered resource.
|
||||
try {
|
||||
container.customResource
|
||||
} catch (e) {
|
||||
// avoid errors when the backend first loads
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default CustomService
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
Make sure to wrap your usage of the new resource in a try-catch block when you use it in a constructor. This is to avoid errors that can arise when the backend first loads, as the resource isn't registered yet.
|
||||
|
||||
:::
|
||||
|
||||
### Note About Services Lifetime
|
||||
|
||||
If you want to access new registrations in the dependency container within a service, you must set the lifetime of the service either to `Lifetime.SCOPED` or `Lifetime.TRANSIENT`. Services that have a `Lifetime.SINGLETON` lifetime can't access new registrations since they're resolved and cached in the root dependency container beforehand. You can learn more in the [Create Services documentation](../services/create-service.mdx#service-life-time).
|
||||
|
||||
For custom services, no additional action is required as the default lifetime is `Lifetime.SCOPED`. However, if you extend a core service, you must change the lifetime since the default lifetime for core services is `Lifetime.SINGLETON`.
|
||||
|
||||
For example:
|
||||
|
||||
<!-- eslint-disable prefer-rest-params -->
|
||||
|
||||
```ts
|
||||
import { Lifetime } from "awilix"
|
||||
import {
|
||||
ProductService as MedusaProductService,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
// extending ProductService from the core
|
||||
class ProductService extends MedusaProductService {
|
||||
// The default life time for a core service is SINGLETON
|
||||
static LIFE_TIME = Lifetime.SCOPED
|
||||
|
||||
constructor(container, options) {
|
||||
super(...arguments)
|
||||
|
||||
// use the registered resource.
|
||||
try {
|
||||
container.customResource
|
||||
} catch (e) {
|
||||
// avoid errors when the backend first loads
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default ProductService
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<DetailsList
|
||||
sections={[
|
||||
{
|
||||
title: 'AwilixResolutionError: Could Not Resolve X',
|
||||
content: <ServiceLifetimeSection />
|
||||
},
|
||||
{
|
||||
title: 'AwilixResolutionError: Could Not Resolve X (Custom Registration)',
|
||||
content: <CustomRegistrationSection />
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Store API reference](https://docs.medusajs.com/api/store)
|
||||
- [Admin API reference](https://docs.medusajs.com/api/admin)
|
||||
@@ -1,950 +0,0 @@
|
||||
---
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# Create Express Endpoint
|
||||
|
||||
In this document, you’ll learn how to create express endpoints in Medusa.
|
||||
|
||||
:::note
|
||||
|
||||
Following v1.17.2 of `@medusajs/medusa`, it's highly recommended to use the [API Routes](./create.mdx) instead. Future versions of Medusa may drop support of Express Endpoints.
|
||||
|
||||
:::
|
||||
|
||||
## Basic Implementation
|
||||
|
||||
To create a new endpoint, start by creating a new file in `src/api` called `index.ts`. At its basic format, `index.ts` should look something like this:
|
||||
|
||||
```ts title="src/api/index.ts"
|
||||
import { Router } from "express"
|
||||
|
||||
export default (rootDirectory, options) => {
|
||||
const router = Router()
|
||||
|
||||
router.get("/hello", (req, res) => {
|
||||
res.json({
|
||||
message: "Welcome to My Store!",
|
||||
})
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
```
|
||||
|
||||
This exports a function that returns an Express router. The function receives two parameters:
|
||||
|
||||
- `rootDirectory` is the absolute path to the root directory that your backend is running from.
|
||||
- `options` is an object that contains the configurations exported from `medusa-config.js`. If your API route is part of a plugin, then it will contain the plugin's options instead.
|
||||
|
||||
---
|
||||
|
||||
## Building Files
|
||||
|
||||
Custom endpoints must be transpiled and moved to the `dist` directory before you can start consuming them. When you run your backend using either the `medusa develop` or `npx medusa develop` commands, it watches the files under `src` for any changes, then triggers the `build` command and restarts the server.
|
||||
|
||||
However, the build isn't triggered when the backend first starts running, and it's never triggered when the `medusa start` or `npx medusa start` commands are used.
|
||||
|
||||
So, make sure to run the `build` command before starting the backend:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Defining Multiple Routes or Middlewares
|
||||
|
||||
Instead of returning an Express router in the function, you can return an array of routes and [middlewares](./add-middleware-express-route.mdx).
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/index.ts"
|
||||
import { Router } from "express"
|
||||
|
||||
export default (rootDirectory, options) => {
|
||||
const router = Router()
|
||||
|
||||
router.get("/hello", (req, res) => {
|
||||
res.json({
|
||||
message: "Welcome to My Store!",
|
||||
})
|
||||
})
|
||||
|
||||
// you can also define the middleware
|
||||
// in another file and import it
|
||||
const middleware = (res, req, next) => {
|
||||
// TODO define global middleware
|
||||
console.log("hello from middleware")
|
||||
next()
|
||||
}
|
||||
|
||||
const anotherRouter = Router()
|
||||
anotherRouter.get("/store/*", (req, res, next) => {
|
||||
// TODO perform an actions for all store endpoints
|
||||
next()
|
||||
})
|
||||
|
||||
return [middleware, router, anotherRouter]
|
||||
}
|
||||
```
|
||||
|
||||
This allows you to export multiple routers and middlewares from the same file. You can also import the routers, routes, and middlewares from other files, then import them in `src/api/index.ts` instead of defining them within the same file.
|
||||
|
||||
---
|
||||
|
||||
## Endpoint Path
|
||||
|
||||
Your endpoint can be under any path you wish.
|
||||
|
||||
By Medusa’s conventions:
|
||||
|
||||
- All Storefront REST APIs are prefixed by `/store`. For example, the `/store/products` endpoint lets you retrieve the products to display them on your storefront.
|
||||
- All Admin REST APIs are prefixed by `/admin`. For example, the `/admin/products` endpoint lets you retrieve the products to display them on your Admin.
|
||||
|
||||
You can also create endpoints that don't reside under these two prefixes, similar to the `hello` endpoint in the previous example.
|
||||
|
||||
---
|
||||
|
||||
## CORS Configuration
|
||||
|
||||
If you’re adding a storefront or admin endpoint and you want to access these endpoints from the storefront or Medusa Admin, you need to pass your endpoints Cross-Origin Resource Origin (CORS) options using the `cors` package.
|
||||
|
||||
First, import the necessary utility functions and types from Medusa's packages with the `cors` package:
|
||||
|
||||
```ts
|
||||
import {
|
||||
getConfigFile,
|
||||
parseCorsOrigins,
|
||||
} from "medusa-core-utils"
|
||||
import {
|
||||
ConfigModule,
|
||||
} from "@medusajs/medusa/dist/types/global"
|
||||
import cors from "cors"
|
||||
```
|
||||
|
||||
Next, in the exported function, retrieve the CORS configurations of your backend using the utility functions you imported:
|
||||
|
||||
```ts
|
||||
export default (rootDirectory) => {
|
||||
// ...
|
||||
|
||||
const { configModule } =
|
||||
getConfigFile<ConfigModule>(rootDirectory, "medusa-config")
|
||||
const { projectConfig } = configModule
|
||||
|
||||
// ....
|
||||
}
|
||||
```
|
||||
|
||||
Then, create an object that will hold the CORS configurations. Based on whether it's storefront or admin CORS options, you pass it the respective configuration from `projectConfig`:
|
||||
|
||||
<Tabs groupId="endpoint-type" isCodeTabs={true}>
|
||||
<TabItem value="storefront" label="Storefront CORS" default>
|
||||
|
||||
```ts
|
||||
const storeCorsOptions = {
|
||||
origin: projectConfig.store_cors.split(","),
|
||||
credentials: true,
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="admin" label="Admin CORS">
|
||||
|
||||
```ts
|
||||
const adminCorsOptions = {
|
||||
origin: projectConfig.admin_cors.split(","),
|
||||
credentials: true,
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Finally, you can either pass the `cors` middleware for a specific route, or pass it to the entire router:
|
||||
|
||||
<Tabs groupId="pass-type" isCodeTabs={true}>
|
||||
<TabItem value="single" label="Pass to Endpoint" default>
|
||||
|
||||
```ts
|
||||
adminRouter.options("/admin/hello", cors(adminCorsOptions))
|
||||
adminRouter.get(
|
||||
"/admin/hello",
|
||||
cors(adminCorsOptions),
|
||||
(req, res) => {
|
||||
// ...
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="router" label="Pass to Router">
|
||||
|
||||
```ts
|
||||
adminRouter.use(cors(adminCorsOptions))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
---
|
||||
|
||||
## Parse Request Body Parameters
|
||||
|
||||
If you want to accept request body parameters, you need to pass express middlewares that parse the payload type to your router.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/index.ts"
|
||||
import bodyParser from "body-parser"
|
||||
import express, { Router } from "express"
|
||||
|
||||
|
||||
export default (rootDirectory, pluginOptions) => {
|
||||
const router = Router()
|
||||
|
||||
router.use(express.json())
|
||||
router.use(express.urlencoded({ extended: true }))
|
||||
|
||||
router.post("/store/hello", (req, res) => {
|
||||
res.json({
|
||||
message: req.body.name,
|
||||
})
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
```
|
||||
|
||||
In the code snippet above, you use the following middlewares:
|
||||
|
||||
- `express.json()`: parses requests with JSON payloads
|
||||
- `express.urlencoded()`: parses requests with urlencoded payloads.
|
||||
|
||||
You can learn about other available middlewares in the [Express documentation](https://expressjs.com/en/api.html#express).
|
||||
|
||||
---
|
||||
|
||||
## Protected Routes
|
||||
|
||||
Protected routes are routes that should only be accessible by logged-in customers or users.
|
||||
|
||||
### Protect Store Routes
|
||||
|
||||
There are two approaches to make a storefront route protected:
|
||||
|
||||
- Using the `requireCustomerAuthentication` middleware, which disallows unauthenticated customers from accessing a route, and allows you to access the logged-in customer's ID.
|
||||
- Using the `authenticateCustomer` middleware, which allows both authenticated and unauthenticated customers to access your route, but allows you to access the logged-in customer's ID as well.
|
||||
|
||||
To make a storefront route protected using either middlewares, first, import the middleware at the top of your file:
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
import { requireCustomerAuthentication } from "@medusajs/medusa"
|
||||
// import { authenticateCustomer } from "@medusajs/medusa"
|
||||
```
|
||||
|
||||
Then, pass the middleware to either a single route or an entire router:
|
||||
|
||||
<Tabs groupId="pass-type" isCodeTabs={true}>
|
||||
<TabItem value="single" label="Pass to Endpoint" default>
|
||||
|
||||
```ts
|
||||
// only necessary if you're passing cors options per route
|
||||
router.options("/store/hello", cors(storeCorsOptions))
|
||||
router.get(
|
||||
"/store/hello",
|
||||
cors(storeCorsOptions),
|
||||
requireCustomerAuthentication(),
|
||||
// authenticateCustomer()
|
||||
async (req, res) => {
|
||||
// access current customer
|
||||
const id = req.user.customer_id
|
||||
// if you're using authenticateCustomer middleware
|
||||
// check if id is set first
|
||||
|
||||
const customerService = req.scope.resolve("customerService")
|
||||
|
||||
const customer = await customerService.retrieve(id)
|
||||
// ...
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="router" label="Pass to Router">
|
||||
|
||||
```ts
|
||||
storeRouter.use(requireCustomerAuthentication())
|
||||
// all routes added to storeRouter are now protected
|
||||
// the logged in customer can be accessed using:
|
||||
// req.user.customer_id
|
||||
|
||||
// storeRouter.use(authenticateCustomer())
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Protect Admin Routes
|
||||
|
||||
To protect admin routes and only allow logged-in users from accessing them, first, import the `authenticate` middleware at the top of the file:
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
import { authenticate } from "@medusajs/medusa"
|
||||
```
|
||||
|
||||
Then, pass the middleware to either a single route or an entire router:
|
||||
|
||||
<Tabs groupId="pass-type" isCodeTabs={true}>
|
||||
<TabItem value="single" label="Pass to Endpoint" default>
|
||||
|
||||
```ts
|
||||
// only necessary if you're passing cors options per route
|
||||
adminRouter.options("/admin/hello", cors(adminCorsOptions))
|
||||
adminRouter.get(
|
||||
"/admin/hello",
|
||||
cors(adminCorsOptions),
|
||||
authenticate(),
|
||||
async (req, res) => {
|
||||
// access current user
|
||||
const id = req.user.userId
|
||||
const userService = req.scope.resolve("userService")
|
||||
|
||||
const user = await userService.retrieve(id)
|
||||
// ...
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="router" label="Pass to Router">
|
||||
|
||||
```ts
|
||||
adminRouter.use(authenticate())
|
||||
// all routes added to adminRouter are now protected
|
||||
// the logged in user can be accessed using:
|
||||
// req.user.userId
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Medusa Config
|
||||
|
||||
As mentioned, the second parameter `options` includes the configurations exported from `medusa-config.js`. However, in plugins it only includes the plugin's options.
|
||||
|
||||
If you need to access the Medusa configuration in your endpoint, you can use the `getConfigFile` method imported from `medusa-core-utils`. It accepts the following parameters:
|
||||
|
||||
1. `rootDirectory`: The first parameter is a string indicating root directory of your Medusa backend.
|
||||
2. `config`: The second parameter is a string indicating the name of the config file, which should be `medusa-config` unless you change it.
|
||||
|
||||
The function returns an object with the following properties:
|
||||
|
||||
1. `configModule`: An object containing the configurations exported from `medusa-config.js`.
|
||||
2. `configFilePath`: A string indicating absolute path to the configuration file.
|
||||
3. `error`: if any errors occur, they'll be included as the value of this property. Otherwise, its value will be `undefined`.
|
||||
|
||||
Here's an example of retrieving the configurations within an endpoint using `getConfigFile`:
|
||||
|
||||
```ts title="src/api/index.ts"
|
||||
import { Router } from "express"
|
||||
import { ConfigModule } from "@medusajs/medusa"
|
||||
import { getConfigFile } from "medusa-core-utils"
|
||||
|
||||
export default (rootDirectory) => {
|
||||
const router = Router()
|
||||
const { configModule } = getConfigFile<ConfigModule>(
|
||||
rootDirectory,
|
||||
"medusa-config"
|
||||
)
|
||||
|
||||
router.get("/store-cors", (req, res) => {
|
||||
res.json({
|
||||
store_cors: configModule.projectConfig.store_cors,
|
||||
})
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
```
|
||||
|
||||
Notice that `getConfigFile` is a generic function. So, if you're using TypeScript, you should pass it the type `ConfigModule` imported from `@medusajs/medusa`.
|
||||
|
||||
If you're accessing custom configurations, you'll need to create a new type that defines these configurations. For example:
|
||||
|
||||
```ts title="src/api/index.ts"
|
||||
import { Router } from "express"
|
||||
import { ConfigModule } from "@medusajs/medusa"
|
||||
import { getConfigFile } from "medusa-core-utils"
|
||||
|
||||
type MyConfigModule = ConfigModule & {
|
||||
projectConfig: {
|
||||
custom_config?: string
|
||||
}
|
||||
}
|
||||
|
||||
export default (rootDirectory) => {
|
||||
const router = Router()
|
||||
const { configModule } = getConfigFile<MyConfigModule>(
|
||||
rootDirectory,
|
||||
"medusa-config"
|
||||
)
|
||||
|
||||
router.get("/hello", (req, res) => {
|
||||
res.json({
|
||||
custom_config: configModule.projectConfig.custom_config,
|
||||
})
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Handle Errors
|
||||
|
||||
As Medusa uses v4 of Express, you need to manually handle errors thrown asynchronously as explained in [Express's documentation](https://expressjs.com/en/guide/error-handling.html).
|
||||
|
||||
You can use [middlewares](./add-middleware-express-route.mdx) to handle errors. You can also use middlewares defined by Medusa, which ensure that your error handling is consistent across your Medusa backend.
|
||||
|
||||
:::note
|
||||
|
||||
Code snippets are taken from the [full example available at the end of this document](#example-crud-endpoints).
|
||||
|
||||
:::
|
||||
|
||||
To handle errors using Medusa's middlewares, first, import the `errorHandler` middleware from `@medusajs/medusa` and apply it on your routers. Make sure it's applied after all other middlewares and routes:
|
||||
|
||||
```ts title="src/api/index.ts"
|
||||
import express, { Router } from "express"
|
||||
import adminRoutes from "./admin"
|
||||
import storeRoutes from "./store"
|
||||
import { errorHandler } from "@medusajs/medusa"
|
||||
|
||||
export default (rootDirectory, options) => {
|
||||
const router = Router()
|
||||
|
||||
router.use(express.json())
|
||||
router.use(express.urlencoded({ extended: true }))
|
||||
|
||||
adminRoutes(router, options)
|
||||
storeRoutes(router, options)
|
||||
|
||||
router.use(errorHandler())
|
||||
|
||||
return router
|
||||
}
|
||||
```
|
||||
|
||||
Then, wrap the function handler of every route with the `wrapHandler` middleware imported from `@medusajs/medusa`. For example:
|
||||
|
||||
```ts title="src/api/admin.ts"
|
||||
import { wrapHandler } from "@medusajs/medusa"
|
||||
|
||||
// ...
|
||||
|
||||
export default function adminRoutes(
|
||||
router: Router,
|
||||
options: ConfigModule
|
||||
) {
|
||||
// ...
|
||||
|
||||
adminRouter.get("/posts", wrapHandler(async (req, res) => {
|
||||
const postService: PostService =
|
||||
req.scope.resolve("postService")
|
||||
|
||||
res.json({
|
||||
posts: await postService.list(),
|
||||
})
|
||||
}))
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively, you can define the endpoints in different files, and import and use them in your router:
|
||||
|
||||
<!-- eslint-disable @typescript-eslint/no-var-requires -->
|
||||
|
||||
```ts title="src/api/admin.ts"
|
||||
import { wrapHandler } from "@medusajs/medusa"
|
||||
|
||||
// ...
|
||||
|
||||
export default function adminRoutes(
|
||||
router: Router,
|
||||
options: ConfigModule
|
||||
) {
|
||||
// ...
|
||||
|
||||
adminRouter.get(
|
||||
"/posts",
|
||||
wrapHandler(require("./list-posts").default)
|
||||
)
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Now all errors thrown in your custom endpoints, including in their custom services, will be caught and returned to the user.
|
||||
|
||||
However, if you throw errors like this:
|
||||
|
||||
```ts
|
||||
throw new Error ("Post was not found")
|
||||
```
|
||||
|
||||
You'll notice that the endpoint returns the following object error in the response:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": "unknown_error",
|
||||
"type": "unknown_error",
|
||||
"message": "An unknown error occurred."
|
||||
}
|
||||
```
|
||||
|
||||
To ensure the error message is relayed in the response, it's recommended to use `MedusaError` imported from `@medusajs/utils` as the thrown error instead:
|
||||
|
||||
```ts
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
|
||||
// ...
|
||||
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
"Post was not found"
|
||||
)
|
||||
```
|
||||
|
||||
The constructor of `MedusaError` accepts the following parameters:
|
||||
|
||||
1. The first parameter is the type of the error. You can use one of the predefined errors under `MedusaError.Types`, such as `MedusaError.Types.NOT_FOUND` which sets the response status code to `404` automatically.
|
||||
2. The second parameter is the message of the error.
|
||||
3. The third parameter is an optional code, which is a string, that can be returned in the error object.
|
||||
|
||||
After using `MedusaError`, you'll notice that the returned error in the response provides a clearer message:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "not_found",
|
||||
"message": "Post was not found"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use Other Resources
|
||||
|
||||
### Entities and Repositories
|
||||
|
||||
Your endpoints likely perform an action on an entity. For example, you may create an endpoint to retrieve a list of posts.
|
||||
|
||||
You can perform actions on an entity either through its [Repository](../entities/overview.mdx#what-are-repositories) or through a [service](#services). This section covers how to retrieve a repository in an endpoint, but it's recommended to use services instead.
|
||||
|
||||
You can retrieve any registered resource, including repositories, in your endpoint using `req.scope.resolve` passing it the resource's registration name in the [dependency container](../fundamentals/dependency-injection.md). Repositories are registered as their camel-case name. So, for example, if you have a `PostRepository`, it's registered as `postRepository`.
|
||||
|
||||
Here’s an example of an endpoint that retrieves the list of posts in a store:
|
||||
|
||||
:::note
|
||||
|
||||
Posts are represented by a custom entity not covered in this guide. You can refer to the [entities](../entities/create.mdx#adding-relations) for more details on how to create a custom entity.
|
||||
|
||||
:::
|
||||
|
||||
```ts
|
||||
import { PostRepository } from "../repositories/post"
|
||||
import { EntityManager } from "typeorm"
|
||||
|
||||
// ...
|
||||
|
||||
export default () => {
|
||||
// ...
|
||||
|
||||
storeRouter.get("/posts", async (req, res) => {
|
||||
const postRepository: typeof PostRepository =
|
||||
req.scope.resolve("postRepository")
|
||||
const manager: EntityManager = req.scope.resolve("manager")
|
||||
const postRepo = manager.withRepository(postRepository)
|
||||
|
||||
return res.json({
|
||||
posts: await postRepo.find(),
|
||||
})
|
||||
})
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Notice that to retrieve an instance of the repository, you need to retrieve first Typeorm's Entity manager and use its `withRepository` method.
|
||||
|
||||
### Services
|
||||
|
||||
Services in Medusa bundle a set of functionalities into one class. Typically, these functionalities are associated with an entity, such as methods to retrieve, create, or update its records.
|
||||
|
||||
You can retrieve any registered resource, including services, in your endpoint using `req.scope.resolve` passing it the service’s registration name in the [dependency container](../fundamentals/dependency-injection.md). Services are registered as their camel-case name. So, for example, if you have a `PostService`, it's registered as `postService`.
|
||||
|
||||
Here’s an example of an endpoint that retrieves the list of posts in a store:
|
||||
|
||||
:::note
|
||||
|
||||
`PostService` is a custom service that is not covered in this guide. You can refer to the [services](../services/create-service.mdx) documentation for more details on how to create a custom service, and find an [example of PostService](../services/create-service.mdx#example-services-with-crud-operations)
|
||||
|
||||
:::
|
||||
|
||||
```ts
|
||||
storeRouter.get("/posts", async (req, res) => {
|
||||
const postService: PostService = req.scope.resolve(
|
||||
"postService"
|
||||
)
|
||||
|
||||
return res.json({
|
||||
posts: await postService.list(),
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Other Resources
|
||||
|
||||
Any resource that is registered in the dependency container, such as strategies or file services, can be accessed through `req.scope.resolve`. Refer to the [dependency injection](../fundamentals/dependency-injection.md) documentation for details on registered resources.
|
||||
|
||||
---
|
||||
|
||||
## Example: CRUD Endpoints
|
||||
|
||||
This section services as an example of creating endpoints that perform Create, Read, Update, and Delete (CRUD) operations. Note that all admin endpoints are placed in `src/api/admin.ts`, and store endpoints are placed in `src/api/store.ts`. You can also place each endpoint in a separate file, import it, and add it to its respective router.
|
||||
|
||||
You can refer to the [Entities](../entities/create.mdx#adding-relations) and [Services](../services/create-service.mdx#example-services-with-crud-operations) documentation to learn how to create the custom entities and services used in this example.
|
||||
|
||||
<Tabs groupId="files" isCodeTabs={true}>
|
||||
<TabItem value="index" label="src/api/index.ts" default>
|
||||
|
||||
```ts
|
||||
import express, { Router } from "express"
|
||||
import adminRoutes from "./admin"
|
||||
import storeRoutes from "./store"
|
||||
import { errorHandler } from "@medusajs/medusa"
|
||||
|
||||
export default (rootDirectory, options) => {
|
||||
const router = Router()
|
||||
|
||||
router.use(express.json())
|
||||
router.use(express.urlencoded({ extended: true }))
|
||||
|
||||
adminRoutes(router, options)
|
||||
storeRoutes(router, options)
|
||||
|
||||
router.use(errorHandler())
|
||||
|
||||
return router
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="admin" label="src/api/admin.ts">
|
||||
|
||||
```ts
|
||||
import { Router } from "express"
|
||||
import PostService from "../services/post"
|
||||
import {
|
||||
ConfigModule,
|
||||
} from "@medusajs/medusa/dist/types/global"
|
||||
import cors from "cors"
|
||||
import { authenticate, wrapHandler } from "@medusajs/medusa"
|
||||
import AuthorService from "../services/author"
|
||||
|
||||
export default function adminRoutes(
|
||||
router: Router,
|
||||
options: ConfigModule
|
||||
) {
|
||||
const { projectConfig } = options
|
||||
|
||||
const corsOptions = {
|
||||
origin: projectConfig.admin_cors.split(","),
|
||||
credentials: true,
|
||||
}
|
||||
|
||||
const adminRouter = Router()
|
||||
|
||||
router.use("/admin/blog", adminRouter)
|
||||
|
||||
adminRouter.use(cors(corsOptions))
|
||||
adminRouter.use(authenticate())
|
||||
|
||||
// it's recommended to define the routes
|
||||
// in separate files. They're done in
|
||||
// the same file here for simplicity
|
||||
|
||||
|
||||
// list all blog posts
|
||||
adminRouter.get(
|
||||
"/posts",
|
||||
wrapHandler(async (req, res) => {
|
||||
const postService: PostService = req.scope.resolve(
|
||||
"postService"
|
||||
)
|
||||
|
||||
res.json({
|
||||
posts: await postService.list(),
|
||||
})
|
||||
}))
|
||||
|
||||
|
||||
// retrieve a single blog post
|
||||
adminRouter.get(
|
||||
"/posts/:id",
|
||||
wrapHandler(async (req, res) => {
|
||||
const postService: PostService = req.scope.resolve(
|
||||
"postService"
|
||||
)
|
||||
|
||||
const post = await postService.retrieve(req.params.id)
|
||||
|
||||
res.json({
|
||||
post,
|
||||
})
|
||||
}))
|
||||
|
||||
// create a blog post
|
||||
adminRouter.post(
|
||||
"/posts",
|
||||
wrapHandler(async (req, res) => {
|
||||
const postService: PostService = req.scope.resolve(
|
||||
"postService"
|
||||
)
|
||||
|
||||
// basic validation of request body
|
||||
if (!req.body.title || !req.body.author_id) {
|
||||
throw new Error("`title` and `author_id` are required.")
|
||||
}
|
||||
|
||||
const post = await postService.create(req.body)
|
||||
|
||||
res.json({
|
||||
post,
|
||||
})
|
||||
}))
|
||||
|
||||
// update a blog post
|
||||
adminRouter.post(
|
||||
"/posts/:id",
|
||||
wrapHandler(async (req, res) => {
|
||||
const postService: PostService = req.scope.resolve(
|
||||
"postService"
|
||||
)
|
||||
|
||||
// basic validation of request body
|
||||
if (req.body.id) {
|
||||
throw new Error("Can't update post ID")
|
||||
}
|
||||
|
||||
const post = await postService.update(
|
||||
req.params.id,
|
||||
req.body
|
||||
)
|
||||
|
||||
res.json({
|
||||
post,
|
||||
})
|
||||
}))
|
||||
|
||||
// delete a blog post
|
||||
adminRouter.delete(
|
||||
"/posts/:id",
|
||||
wrapHandler(async (req, res) => {
|
||||
const postService: PostService = req.scope.resolve(
|
||||
"postService"
|
||||
)
|
||||
|
||||
await postService.delete(req.params.id)
|
||||
|
||||
res.status(200).end()
|
||||
}))
|
||||
|
||||
// list all blog authors
|
||||
adminRouter.get(
|
||||
"/authors",
|
||||
wrapHandler(async (req, res) => {
|
||||
const authorService: AuthorService = req.scope.resolve(
|
||||
"authorService"
|
||||
)
|
||||
|
||||
res.json({
|
||||
authors: await authorService.list(),
|
||||
})
|
||||
}))
|
||||
|
||||
// retrieve a single blog author
|
||||
adminRouter.get(
|
||||
"/authors/:id",
|
||||
wrapHandler(async (req, res) => {
|
||||
const authorService: AuthorService = req.scope.resolve(
|
||||
"authorService"
|
||||
)
|
||||
|
||||
res.json({
|
||||
post: await authorService.retrieve(req.params.id),
|
||||
})
|
||||
}))
|
||||
|
||||
// create a blog author
|
||||
adminRouter.post(
|
||||
"/authors",
|
||||
wrapHandler(async (req, res) => {
|
||||
const authorService: AuthorService = req.scope.resolve(
|
||||
"authorService"
|
||||
)
|
||||
|
||||
// basic validation of request body
|
||||
if (!req.body.name) {
|
||||
throw new Error("`name` is required.")
|
||||
}
|
||||
|
||||
const author = await authorService.create(req.body)
|
||||
|
||||
res.json({
|
||||
author,
|
||||
})
|
||||
}))
|
||||
|
||||
// update a blog author
|
||||
adminRouter.post(
|
||||
"/authors/:id",
|
||||
wrapHandler(async (req, res) => {
|
||||
const authorService: AuthorService = req.scope.resolve(
|
||||
"authorService"
|
||||
)
|
||||
|
||||
// basic validation of request body
|
||||
if (req.body.id) {
|
||||
throw new Error("Can't update author ID")
|
||||
}
|
||||
|
||||
const author = await authorService.update(
|
||||
req.params.id,
|
||||
req.body
|
||||
)
|
||||
|
||||
res.json({
|
||||
author,
|
||||
})
|
||||
}))
|
||||
|
||||
// delete a blog author
|
||||
adminRouter.delete(
|
||||
"/authors/:id",
|
||||
wrapHandler(async (req, res) => {
|
||||
const authorService: AuthorService = req.scope.resolve(
|
||||
"authorService"
|
||||
)
|
||||
|
||||
await authorService.delete(req.params.id)
|
||||
|
||||
res.status(200).end()
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="store" label="src/api/store.ts">
|
||||
|
||||
```ts
|
||||
import { Router } from "express"
|
||||
import {
|
||||
ConfigModule,
|
||||
} from "@medusajs/medusa/dist/types/global"
|
||||
import PostService from "../services/post"
|
||||
import cors from "cors"
|
||||
import AuthorService from "../services/author"
|
||||
import { wrapHandler } from "@medusajs/medusa"
|
||||
|
||||
export default function storeRoutes(
|
||||
router: Router,
|
||||
options: ConfigModule
|
||||
) {
|
||||
const { projectConfig } = options
|
||||
|
||||
const storeCorsOptions = {
|
||||
origin: projectConfig.store_cors.split(","),
|
||||
credentials: true,
|
||||
}
|
||||
|
||||
const storeRouter = Router()
|
||||
router.use("/store/blog", storeRouter)
|
||||
|
||||
storeRouter.use(cors(storeCorsOptions))
|
||||
|
||||
// list all blog posts
|
||||
storeRouter.get(
|
||||
"/posts",
|
||||
wrapHandler(async (req, res) => {
|
||||
const postService: PostService = req.scope.resolve(
|
||||
"postService"
|
||||
)
|
||||
|
||||
res.json({
|
||||
posts: await postService.list(),
|
||||
})
|
||||
}))
|
||||
|
||||
// retrieve a single blog post
|
||||
storeRouter.get(
|
||||
"/posts/:id",
|
||||
wrapHandler(async (req, res) => {
|
||||
const postService: PostService = req.scope.resolve(
|
||||
"postService"
|
||||
)
|
||||
|
||||
res.json({
|
||||
post: await postService.retrieve(req.params.id),
|
||||
})
|
||||
}))
|
||||
|
||||
// list all blog authors
|
||||
storeRouter.get(
|
||||
"/authors",
|
||||
wrapHandler(async (req, res) => {
|
||||
const authorService: AuthorService = req.scope.resolve(
|
||||
"authorService"
|
||||
)
|
||||
|
||||
res.json({
|
||||
authors: await authorService.list(),
|
||||
})
|
||||
}))
|
||||
|
||||
// retrieve a single blog author
|
||||
storeRouter.get(
|
||||
"/authors/:id",
|
||||
wrapHandler(async (req, res) => {
|
||||
const authorService: AuthorService = req.scope.resolve(
|
||||
"authorService"
|
||||
)
|
||||
|
||||
res.json({
|
||||
post: await authorService.retrieve(req.params.id),
|
||||
})
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Storefront API Reference](https://docs.medusajs.com/api/store)
|
||||
- [Admin API Reference](https://docs.medusajs.com/api/admin)
|
||||
@@ -1,860 +0,0 @@
|
||||
---
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Create an API Route
|
||||
|
||||
In this document, you’ll learn how to create API Routes in Medusa.
|
||||
|
||||
:::tip
|
||||
|
||||
v1.17.2 of `@medusajs/medusa` introduced API Routes to replace Express endpoints. You can still use the [Express endpoints approach](./create-express-route.mdx), however, it's highly recommended that you start using API Routes.
|
||||
|
||||
:::
|
||||
|
||||
## Basic Implementation
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export const GET = (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
res.json({
|
||||
message: "[GET] Hello world!",
|
||||
})
|
||||
}
|
||||
|
||||
export const POST = (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
res.json({
|
||||
message: "[POST] Hello world!",
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### API Route Path
|
||||
|
||||
Custom API Routes must be created in a file named `route.ts` or `route.js` under the `src/api` directory of your Medusa backend or plugin. The API Route's path will be the same as the path of its corresponding `route.ts` file relative to `src/api`.
|
||||
|
||||
For example, if you're creating the API route `store/custom`, you must create the file `src/api/store/custom/route.ts`.
|
||||
|
||||
### API Route Method
|
||||
|
||||
`route.ts` can export at least one of the following method handler functions: `GET`, `POST`, `DELETE`, `PUT`, `PATCH`, `OPTIONS`, and `HEAD`. Defining these method handlers adds a new API Route for the corresponding HTTP method at the same path.
|
||||
|
||||
Each of these method handler functions receives two parameters: the `MedusaRequest` which extends Express's [Request](https://expressjs.com/en/api.html#req), and the `MedusaResponse` which extends [Response](https://expressjs.com/en/api.html#res). Both are imported from `@medusajs/medusa`.
|
||||
|
||||
In the example above, `GET` and `POST` API Routes will be added at the `store/custom` path.
|
||||
|
||||
---
|
||||
|
||||
## Building Files
|
||||
|
||||
Custom API Routes must be transpiled and moved to the `dist` directory before you can start consuming them. When you run your backend using either the `medusa develop` or `npx medusa develop` commands, it watches the files under `src` for any changes, then triggers the `build` command and restarts the server.
|
||||
|
||||
However, the build isn't triggered when the backend first starts running, and it's never triggered when the `medusa start` or `npx medusa start` commands are used.
|
||||
|
||||
So, make sure to run the `build` command before starting the backend and testing out your API Routes:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Medusa API Routes Path Convention
|
||||
|
||||
Although your API Route can be under any path you wish, the Medusa backend uses the following conventions:
|
||||
|
||||
- All storefront REST APIs are prefixed by `/store`. For example, the `/store/products` API Route lets you retrieve the products to display them on your storefront.
|
||||
- All admin REST APIs are prefixed by `/admin`. For example, the `/admin/products` API Route lets you retrieve the products to display them on your admin.
|
||||
|
||||
---
|
||||
|
||||
## Path Parameters
|
||||
|
||||
If your API Route accepts a path parameter, you can place its route file inside a directory with the name `[<PARAMETER_NAME>]`, where `<PARAMETER_NAME>` is the name of your parameter.
|
||||
|
||||
For example, to add an API Route at the path `store/custom/[id]`, create the route file at `src/api/store/custom/[id]/route.ts`.
|
||||
|
||||
You can access a path parameter's value in method handlers using the `MedusaRequest` object's `params` property, which is an object. Each of the `params` keys is a path parameter's name, and its value is the supplied value when sending the request to the API route.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/store/custom/[id]/route.ts"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const id = req.params.id
|
||||
|
||||
// do something with the ID.
|
||||
}
|
||||
```
|
||||
|
||||
An API Route can have more than one path parameter, but each path parameter's nam is unique. If the same path parameter name is used more than once in the same route path, it results in an error and the Medusa Backend won't register the API Route.
|
||||
|
||||
For example, if your API route accepts an author ID and a post ID, the path to your route file can be `src/api/author/[id]/posts/[post_id]/route.ts`. You can then use the `MedusaRequest` object's `params.id` and `params.post_id` to access the values of the path parameters.
|
||||
|
||||
---
|
||||
|
||||
## CORS Configuration
|
||||
|
||||
The `cors` middleware, which enables Cross-Origin Resource Sharing (CORS), is automatically applied on custom API Routes defined under the `/store` or `/admin` path prefixes based on the respective [store_cors and admin_cors configurations](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx#admin_cors).
|
||||
|
||||
To add CORS configurations to custom API routes under other path prefixes, or override the CORS configurations added by default, define a [middleware](./add-middleware.mdx) on your API routes and pass it the `cors` middleware. For example:
|
||||
|
||||
```ts title="src/api/middlewares.ts"
|
||||
import type {
|
||||
MiddlewaresConfig,
|
||||
} from "@medusajs/medusa"
|
||||
import cors from "cors"
|
||||
|
||||
export const config: MiddlewaresConfig = {
|
||||
routes: [
|
||||
{
|
||||
matcher: "/custom*",
|
||||
middlewares: [
|
||||
cors({
|
||||
origin: "*",
|
||||
credentials: true,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### CORS Opt-Out
|
||||
|
||||
To disable the `cors` middleware for an API Route, export a `CORS` variable in the route file with its value set to `false`.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export const GET = (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
res.json({
|
||||
message: "[GET] Hello world!",
|
||||
})
|
||||
}
|
||||
|
||||
export const CORS = false
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Parse Request Body Parameters
|
||||
|
||||
By default, the Medusa backend parses the body of all requests sent to your API Routes with the `Content-Type` header set to `application/json` to a JavaScript object. Then, the parsed data is attached to the `MedusaRequest` object's `body` property, which is an object.
|
||||
|
||||
Each of the `body`'s keys are a name of the request body parameters, and its value is the passed value in the request body.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export const POST = (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const name = req.body.name
|
||||
|
||||
// do something with the data...
|
||||
}
|
||||
```
|
||||
|
||||
If you want to parse other content types, such as `application/x-www-form-urlencoded`, you have to add a [middleware](./add-middleware.mdx) to your API routes that parses that body type.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/middlewares.ts"
|
||||
import type {
|
||||
MiddlewaresConfig,
|
||||
} from "@medusajs/medusa"
|
||||
import {
|
||||
urlencoded,
|
||||
} from "body-parser"
|
||||
|
||||
export const config: MiddlewaresConfig = {
|
||||
routes: [
|
||||
{
|
||||
matcher: "/store/*",
|
||||
middlewares: [
|
||||
urlencoded({ extended: true }),
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
Note that the `urlencoded` middleware imported from the [body-parser package](https://www.npmjs.com/package/body-parser) attaches the parsed data to the `MedusaRequest` object's `body` property as well.
|
||||
|
||||
### Parse Webhook Body Parameters
|
||||
|
||||
For webhook API Routes, you may need to use the `raw` body parser middleware rather than the default `json`.
|
||||
|
||||
You can opt out of the default body parser by setting the `bodyParser` property of a [middleware route object](./add-middleware.mdx) to `false`, and passing the preferred body-parser middleware in the `middlewares` property.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/middlewares.ts"
|
||||
import { MiddlewaresConfig } from "@medusajs/medusa"
|
||||
import { raw } from "body-parser"
|
||||
|
||||
export const config: MiddlewaresConfig = {
|
||||
routes: [
|
||||
{
|
||||
matcher: "/webhooks/*",
|
||||
bodyParser: false,
|
||||
middlewares: [raw({ type: "application/json" })],
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
You can also disable the default `json` body parser for specific HTTP methods using the `method` property for a middleware route object. Its value can either be a string or an array of strings, each being an HTTP method name.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/middlewares.ts"
|
||||
import { MiddlewaresConfig } from "@medusajs/medusa"
|
||||
import { raw } from "body-parser"
|
||||
|
||||
export const config: MiddlewaresConfig = {
|
||||
routes: [
|
||||
{
|
||||
method: ["POST", "PUT"],
|
||||
matcher: "/webhooks/*",
|
||||
bodyParser: false,
|
||||
middlewares: [raw({ type: "application/json" })],
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
This disables the default JSON body parser for the POST and PUT HTTP methods on API Routes matching the `/webhooks/*` matcher, and applies the `raw` body-parser middleware on them instead.
|
||||
|
||||
### Configure Request Body Size Limit
|
||||
|
||||
By default, the maximum request body size allowed is `1000` bytes. If a request body's size is greater than that, an error is thrown.
|
||||
|
||||
If you expect the request body of an API Route to be larger than the default, you can set the `bodyParser` property of a [middleware route object](./add-middleware.mdx) to a configuration object with the property `sizeLimit`. Its value is a number indicating the maximum allowed size limit in bytes.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/middlewares.ts"
|
||||
import { MiddlewaresConfig } from "@medusajs/medusa"
|
||||
|
||||
export const config: MiddlewaresConfig = {
|
||||
routes: [
|
||||
{
|
||||
bodyParser: { sizeLimit: 2000 }, // in bytes
|
||||
// ...
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Protected API Routes
|
||||
|
||||
Protected API routes are routes that should only be accessible by logged-in customers or users.
|
||||
|
||||
### Protect Store API Routes
|
||||
|
||||
By default, API routes prefixed by `/store` don't require customer authentication to access the API route. However, you can still access the logged-in customer's ID in the API Route method handler using the `MedusaRequest` object's `user.customer_id`, which will be `undefined` if the customer isn't logged in.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { CustomerService } from "@medusajs/medusa"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const id = req.user.customer_id
|
||||
|
||||
if (!id) {
|
||||
// TODO handle not logged in
|
||||
// customers based on the custom
|
||||
// API route's functionality
|
||||
}
|
||||
|
||||
const customerService = req.scope.resolve<CustomerService>(
|
||||
"customerService"
|
||||
)
|
||||
|
||||
const customer = await customerService.retrieve(id)
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
API Routes prefixed by `/store/me`, on the other hand, require customer authentication to access the API Route. You can access the logged-in customer's ID in the API Route method handler using the `MedusaRequest` object's `user.customer_id`.
|
||||
|
||||
If you want to disable authentication requirement on your custom API Route prefixed with `/store/me`, export an `AUTHENTICATE` variable in the route file with its value set to `false`. For example:
|
||||
|
||||
```ts title="src/api/store/me/custom/route.ts"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
res.json({
|
||||
message: "Hello",
|
||||
})
|
||||
}
|
||||
|
||||
export const AUTHENTICATE = false
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
This disables authentication requirement on all API Route methods defined in the same file.
|
||||
|
||||
:::
|
||||
|
||||
### Protect Admin API Routes
|
||||
|
||||
By default, all API Routes prefixed by `/admin` require admin user authentication to access the API Route. You can access the logged-in user's ID in the API Route method handler using the `MedusaRequest` object's `user.userId`.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/admin/custom/route.ts"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
UserService,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const id = req.user.userId
|
||||
|
||||
const userService = req.scope.resolve<UserService>(
|
||||
"userService"
|
||||
)
|
||||
|
||||
const user = await userService.retrieve(id)
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
To disable authentication requirement on an admin API Route, export an `AUTHENTICATE` variable in your route file with its value set to `false`.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/admin/custom/route.ts"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
res.json({
|
||||
message: "Hello",
|
||||
})
|
||||
}
|
||||
|
||||
export const AUTHENTICATE = false
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
This disables authentication requirement on all API Route methods defined in the same file.
|
||||
|
||||
:::
|
||||
|
||||
### Protect Other API Routes
|
||||
|
||||
To protect API routes that aren't prefixed with `/store` or `/admin`, you can use one of the following middlewares exported by `@medusajs/medusa` for authenticating customers or users:
|
||||
|
||||
- `authenticate`: this middleware ensures that only authenticated admin users can access an API Route. You can access the user's ID in the API Route method handler using the `MedusaRequest` object's `user.userId`.
|
||||
- `authenticateCustomer`: this middleware doesn't require a customer to be authenticated, but if a customer is logged in, it attaches their ID to the `MedusaRequest` object's `user.customer_id`.
|
||||
- `requireCustomerAuthentication`: this middleware ensures that only authenticated customers can access an API Route. You can access the customer's ID in the API Route method handler using the `MedusaRequest` object's `user.customer_id`.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/middlewares.ts"
|
||||
import {
|
||||
authenticate,
|
||||
requireCustomerAuthentication,
|
||||
type MiddlewaresConfig,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export const config: MiddlewaresConfig = {
|
||||
routes: [
|
||||
{
|
||||
matcher: "/custom/admin*",
|
||||
middlewares: [authenticate()],
|
||||
},
|
||||
{
|
||||
matcher: "/custom/customer*",
|
||||
middlewares: [requireCustomerAuthentication()],
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Medusa Config
|
||||
|
||||
You can access the configurations exported in `medusa-config.js`, including your custom configurations, by resolving the `configModule` resource using [dependency injection](../fundamentals/dependency-injection.md).
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
import { ConfigModule } from "@medusajs/medusa"
|
||||
|
||||
// This is only helpful if you're
|
||||
// accessing custom configurations
|
||||
// otherwise it's fine to just use `ConfigModule`
|
||||
type MyConfigModule = ConfigModule & {
|
||||
projectConfig: {
|
||||
custom_config?: string
|
||||
}
|
||||
}
|
||||
|
||||
export const GET = (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const configModule = req.scope.resolve<MyConfigModule>(
|
||||
"configModule"
|
||||
)
|
||||
res.json({
|
||||
message: configModule.projectConfig.custom_config,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Handle Errors
|
||||
|
||||
Medusa automatically applies an error-handler middleware on your custom API routes, which returns errors as a JSON response whose format is consistent with the Medusa backend.
|
||||
|
||||
:::tip
|
||||
|
||||
When an error is thrown, the response status code is set to `500` by default. However, that changes based on the `MedusaError` type thrown as explained in the next section.
|
||||
|
||||
:::
|
||||
|
||||
### Medusa Error Types
|
||||
|
||||
You must throw errors of type `MedusaError` to ensure the error message is returned in the response. Otherwise, the returned error message will be `Unknown Error`.
|
||||
|
||||
For example, if you throw an error like this:
|
||||
|
||||
```ts
|
||||
throw new Error ("Post was not found")
|
||||
```
|
||||
|
||||
The API Route returns the following object error in the response:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": "unknown_error",
|
||||
"type": "unknown_error",
|
||||
"message": "An unknown error occurred."
|
||||
}
|
||||
```
|
||||
|
||||
To ensure your error message is relayed in the response, use `MedusaError` imported from `@medusajs/utils` as the thrown error type instead.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
|
||||
// ...
|
||||
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
"Post was not found"
|
||||
)
|
||||
```
|
||||
|
||||
The constructor of `MedusaError` accepts the following parameters:
|
||||
|
||||
1. The first parameter is the error's type. You can use one of the predefined errors under `MedusaError.Types`, such as `MedusaError.Types.NOT_FOUND` which sets the response status code to `404` automatically.
|
||||
2. The second parameter is the message of the error.
|
||||
3. The third parameter is an optional code, which is a string, that's returned in the error object.
|
||||
|
||||
After using `MedusaError`, the returned error in the response provides a clearer message:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "not_found",
|
||||
"message": "Post was not found"
|
||||
}
|
||||
```
|
||||
|
||||
<Details>
|
||||
<Summary>Available MedusaError Types and their respective status codes</Summary>
|
||||
|
||||
The default response code is `500` unless mentioned otherwise.
|
||||
|
||||
- `MedusaError.Types.DB_ERROR`: Sets the response code to `500`.
|
||||
- `MedusaError.Types.DUPLICATE_ERROR`: Sets the response code to `422`.
|
||||
- `MedusaError.Types.INVALID_ARGUMENT`
|
||||
- `MedusaError.Types.INVALID_DATA`: Sets the response code to `400`.
|
||||
- `MedusaError.Types.UNAUTHORIZED`: Sets the response code to `401`.
|
||||
- `MedusaError.Types.NOT_FOUND`: Sets the response code to `404`.
|
||||
- `MedusaError.Types.NOT_ALLOWED`: Sets the response code to `400`.
|
||||
- `MedusaError.Types.UNEXPECTED_STATE`
|
||||
- `MedusaError.Types.CONFLICT`: Sets the response code to `409`.
|
||||
- `MedusaError.Types.PAYMENT_AUTHORIZATION_ERROR`: Sets the response code to `422`.
|
||||
|
||||
</Details>
|
||||
|
||||
### Override Error Handler
|
||||
|
||||
To override the default error handler, pass the `errorHandler` property to the [exported middleware configurations](./add-middleware.mdx) with the custom error-handler middleware as its value.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/middlewares.ts"
|
||||
import { MiddlewaresConfig } from "@medusajs/medusa"
|
||||
|
||||
export const config: MiddlewaresConfig = {
|
||||
errorHandler: (err, req, res, next) => {
|
||||
// custom error handling logic...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Disable Default Error Handler
|
||||
|
||||
To disable the default error handler, set the `errorHandler` property of the [exported middleware configurations](./add-middleware.mdx) to `false`.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/middlewares.ts"
|
||||
import { MiddlewaresConfig } from "@medusajs/medusa"
|
||||
|
||||
export const config: MiddlewaresConfig = {
|
||||
errorHandler: false,
|
||||
}
|
||||
```
|
||||
|
||||
However, when you disable the default error handler, errors thrown in an `async` function or method are not handled and requests goes on indefinitely with no response.
|
||||
|
||||
To ensure that errors are still returned in the response when the default error handler is disabled, either create a custom error handler or wrap your API Route with the `wrapHandler` imported from `@medusajs/medusa`.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/middlewares.ts"
|
||||
import {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
wrapHandler,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export const GET = wrapHandler(async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> => {
|
||||
throw new Error("An error occurred")
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use Other Resources
|
||||
|
||||
Resources, such as services, that are registered in the [dependency container](../fundamentals/dependency-injection.md) can be retrieved in an API Route's handler method using the `MedusaRequest` object's `scope.resolve` method.
|
||||
|
||||
The `scope` method accepts as a parameter the resource's registration name in the [dependency container](../fundamentals/dependency-injection.md).
|
||||
|
||||
### Example: Retrieve Repository
|
||||
|
||||
:::tip
|
||||
|
||||
Posts are represented by a custom entity not covered in this guide. You can refer to the [entities](../entities/create.mdx#adding-relations) for more details on how to create a custom entity.
|
||||
|
||||
:::
|
||||
|
||||
```ts title="src/api/store/posts/route.ts"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
import {
|
||||
PostRepository,
|
||||
} from "../../../repositories/post"
|
||||
import { EntityManager } from "typeorm"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const postRepository =
|
||||
req.scope.resolve<PostRepository>("postRepository")
|
||||
const manager = req.scope.resolve<EntityManager>("manager")
|
||||
const postRepo = manager.withRepository(postRepository)
|
||||
|
||||
res.json({
|
||||
posts: await postRepo.find(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Notice that to retrieve an instance of the repository, you need to retrieve first Typeorm's Entity Manager from the dependency container, then use its `withRepository` method.
|
||||
|
||||
### Example: Retrieve Service
|
||||
|
||||
:::note
|
||||
|
||||
`PostService` is a custom service that is not covered in this guide. You can refer to the [services](../services/create-service.mdx) documentation for more details on how to create a custom service, and find an [example of PostService](../services/create-service.mdx#example-services-with-crud-operations)
|
||||
|
||||
:::
|
||||
|
||||
```ts title="src/api/store/posts/route.ts"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
import { PostService } from "../../../services/post"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const postService: PostService = req.scope.resolve(
|
||||
"postService"
|
||||
)
|
||||
|
||||
res.json({
|
||||
posts: await postService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Ignored Files and Directories
|
||||
|
||||
Files and directories prefixed with `_` are ignored. This can be helpful if you want to implement API Route method handlers in different files, then reference them in your `route.ts` file.
|
||||
|
||||
For example:
|
||||
|
||||
<Tabs groupId="files" isCodeTabs={true}>
|
||||
<TabItem value="custom-route" label="src/api/custom/route.ts" default>
|
||||
|
||||
```ts
|
||||
import getProducts from "../_methods/get-products"
|
||||
|
||||
export const GET = getProducts
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="internal-method" label="src/api/_methods/get-product.ts">
|
||||
|
||||
```ts
|
||||
import {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
ProductService,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export default async function (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const productService = req.scope.resolve<ProductService>(
|
||||
"productService"
|
||||
)
|
||||
|
||||
const products = await productService.list({})
|
||||
|
||||
res.json({
|
||||
products,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
---
|
||||
|
||||
## Example: CRUD API Routes
|
||||
|
||||
This section provides an example of creating API Routes that perform Create, Read, Update, and Delete (CRUD) operations.
|
||||
|
||||
:::note
|
||||
|
||||
You can refer to the [Entities](../entities/create.mdx#adding-relations) and [Services](../services/create-service.mdx#example-services-with-crud-operations) documentation to learn how to create the custom entities and services used in this example.
|
||||
|
||||
:::
|
||||
|
||||
<Tabs groupId="files" isCodeTabs={true}>
|
||||
<TabItem value="posts-routes" label="src/api/admin/posts/route.ts" default>
|
||||
|
||||
```ts
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
import { PostService } from "../../../services/post"
|
||||
|
||||
// list posts
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const postService: PostService = req.scope.resolve(
|
||||
"postService"
|
||||
)
|
||||
|
||||
res.json({
|
||||
posts: await postService.list(),
|
||||
})
|
||||
}
|
||||
|
||||
// create a post
|
||||
export const POST = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const postService: PostService = req.scope.resolve(
|
||||
"postService"
|
||||
)
|
||||
|
||||
// basic validation of request body
|
||||
if (!req.body.title || !req.body.author_id) {
|
||||
throw new Error("`title` and `author_id` are required.")
|
||||
}
|
||||
|
||||
const post = await postService.create(req.body)
|
||||
|
||||
res.json({
|
||||
post,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="posts-id-routes" label="src/api/admin/posts/[id]/route.ts">
|
||||
|
||||
```ts
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
import { PostService } from "../../../services/post"
|
||||
|
||||
// retrieve a post by its ID
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const postService: PostService = req.scope.resolve(
|
||||
"postService"
|
||||
)
|
||||
|
||||
const post = await postService.retrieve(req.params.id)
|
||||
|
||||
res.json({
|
||||
post,
|
||||
})
|
||||
}
|
||||
|
||||
// update a post by its ID
|
||||
export const POST = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const postService: PostService = req.scope.resolve(
|
||||
"postService"
|
||||
)
|
||||
|
||||
// basic validation of request body
|
||||
if (req.body.id) {
|
||||
throw new Error("Can't update post ID")
|
||||
}
|
||||
|
||||
const post = await postService.update(
|
||||
req.params.id,
|
||||
req.body
|
||||
)
|
||||
|
||||
res.json({
|
||||
post,
|
||||
})
|
||||
}
|
||||
|
||||
// delete a post by its ID
|
||||
export const DELETE = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const postService: PostService = req.scope.resolve(
|
||||
"postService"
|
||||
)
|
||||
|
||||
await postService.delete(req.params.id)
|
||||
|
||||
res.status(200).end()
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Storefront API Reference](https://docs.medusajs.com/api/store)
|
||||
- [Admin API Reference](https://docs.medusajs.com/api/admin)
|
||||
@@ -1,172 +0,0 @@
|
||||
---
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import DetailsList from '@site/src/components/DetailsList'
|
||||
import ServiceLifetimeSection from '../../troubleshooting/awilix-resolution-error/_service-lifetime.md'
|
||||
import CustomRegistrationSection from '../../troubleshooting/awilix-resolution-error/_custom-registration.md'
|
||||
|
||||
# Example: Access Logged-In User
|
||||
|
||||
This document gives an example of how you can use middlewares to register the logged-in user in the dependency container of your commerce application. You can then access the logged-in user in other resources, such as services.
|
||||
|
||||
:::tip
|
||||
|
||||
You can apply the same steps if you want to register the current customer.
|
||||
|
||||
:::
|
||||
|
||||
Learn more about [middlewares in its guide](./add-middleware.mdx).
|
||||
|
||||
## Step 1: Create the Middleware
|
||||
|
||||
Create the file `src/api/middlewares.ts` with the following content:
|
||||
|
||||
```ts title="src/api/middlewares.ts"
|
||||
import {
|
||||
authenticate,
|
||||
} from "@medusajs/medusa"
|
||||
import type {
|
||||
MedusaNextFunction,
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
MiddlewaresConfig,
|
||||
User,
|
||||
UserService,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
const registerLoggedInUser = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse,
|
||||
next: MedusaNextFunction
|
||||
) => {
|
||||
let loggedInUser: User | null = null
|
||||
|
||||
if (req.user && req.user.userId) {
|
||||
const userService =
|
||||
req.scope.resolve("userService") as UserService
|
||||
loggedInUser = await userService.retrieve(req.user.userId)
|
||||
}
|
||||
|
||||
req.scope.register({
|
||||
loggedInUser: {
|
||||
resolve: () => loggedInUser,
|
||||
},
|
||||
})
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
export const config: MiddlewaresConfig = {
|
||||
routes: [
|
||||
{
|
||||
matcher: "/admin/products",
|
||||
middlewares: [authenticate(), registerLoggedInUser],
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
This creates a `registerLoggedInUser` middleware that handles registering the logged-in user in the dependency container.
|
||||
|
||||
It then applies this middleware on the `/admin/products` API Route. You can change that to be the route of any other API Route or a pattern that matches multiple routes.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Use in a Service
|
||||
|
||||
Access the logged-in user resource in the constructor of a service:
|
||||
|
||||
<!-- eslint-disable prefer-rest-params -->
|
||||
|
||||
```ts
|
||||
import { Lifetime } from "awilix"
|
||||
import {
|
||||
TransactionBaseService,
|
||||
User,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
class HelloService extends TransactionBaseService {
|
||||
|
||||
protected readonly loggedInUser_: User | null
|
||||
|
||||
constructor(container, options) {
|
||||
super(...arguments)
|
||||
|
||||
try {
|
||||
this.loggedInUser_ = container.loggedInUser
|
||||
} catch (e) {
|
||||
// avoid errors when backend first runs
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default HelloService
|
||||
```
|
||||
|
||||
If you're accessing it in an extended core service, it’s important to change the lifetime of the service to `Lifetime.SCOPED`. For example:
|
||||
|
||||
<!-- eslint-disable prefer-rest-params -->
|
||||
|
||||
```ts
|
||||
import { Lifetime } from "awilix"
|
||||
import {
|
||||
ProductService as MedusaProductService,
|
||||
User,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
// extend core product service
|
||||
class ProductService extends MedusaProductService {
|
||||
// The default life time for a core service is SINGLETON
|
||||
static LIFE_TIME = Lifetime.SCOPED
|
||||
|
||||
protected readonly loggedInUser_: User | null
|
||||
|
||||
constructor(container, options) {
|
||||
super(...arguments)
|
||||
|
||||
this.loggedInUser_ = container.loggedInUser
|
||||
}
|
||||
}
|
||||
|
||||
export default ProductService
|
||||
```
|
||||
|
||||
You can learn more about the importance of changing the service lifetime in the [Middlewares documentation](./add-middleware.mdx#note-about-services-lifetime).
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Test it Out
|
||||
|
||||
To test out your implementation, run the following command in the root directory of the Medusa backend to transpile your changes:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
Then, run your backend with the following command:
|
||||
|
||||
```bash npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
If you try accessing the API Routes you added the middleware to, you should see your implementation working as expected.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<DetailsList
|
||||
sections={[
|
||||
{
|
||||
title: 'AwilixResolutionError: Could Not Resolve X',
|
||||
content: <ServiceLifetimeSection />
|
||||
},
|
||||
{
|
||||
title: 'AwilixResolutionError: Could Not Resolve X (Custom Registration)',
|
||||
content: <CustomRegistrationSection />
|
||||
}
|
||||
]}
|
||||
/>
|
||||
@@ -1,82 +0,0 @@
|
||||
---
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Extend an API Route Validator
|
||||
|
||||
In this guide, you'll learn how to extend an API Route validator from the Medusa core.
|
||||
|
||||
## Overview
|
||||
|
||||
Request fields passed to API Routes defined in the Medusa core are validated to ensure that only expected fields are passed, and the passed fields are of correct types.
|
||||
|
||||
In some scenarios, you may need to allow passing custom fields into an existing API Route. If a custom field is passed to an API Route in the core, the API Route returns an error in the response.
|
||||
|
||||
To allow passing custom fields into core API Routes, you must extend Validators. Validators are classes that are used by the core to validate the request parameters to an API Route.
|
||||
|
||||
This guide explains how to extend a validator to allow passing custom fields to an API Route. You'll be extending the validator of the admin API Create Product route as an example.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
This guide assumes you already have a Medusa backend installed and configured. If not, you can check out the [backend quickstart guide](../backend/install.mdx).
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create File
|
||||
|
||||
You can add the code to extend the validator in any file under the `src` directory of your Medusa project, but it should be executed by `src/api/index.ts`.
|
||||
|
||||
For example, you can add the code in an exported function defined in the file `src/api/routes/admin/products/create-product.ts`, then import that file in `src/api/index.ts` and execute the function.
|
||||
|
||||
For simplicity, this guide adds the code directly in `src/api/index.ts`. Make sure to create it if it's not already created.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Extend Validator
|
||||
|
||||
In the file you created, which in this case is `src/api/index.ts`, add the following content to extend the validator:
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts title="src/api/index.ts"
|
||||
import { registerOverriddenValidators } from "@medusajs/medusa"
|
||||
import {
|
||||
AdminPostProductsReq as MedusaAdminPostProductsReq,
|
||||
} from "@medusajs/medusa/dist/api/routes/admin/products/create-product"
|
||||
import { IsString } from "class-validator"
|
||||
|
||||
class AdminPostProductsReq extends MedusaAdminPostProductsReq {
|
||||
@IsString()
|
||||
custom_field: string
|
||||
}
|
||||
|
||||
registerOverriddenValidators(AdminPostProductsReq)
|
||||
```
|
||||
|
||||
In this code snippet you:
|
||||
|
||||
1. Import the `registerOverriddenValidators` function from the `@medusajs/medusa` package. This utility function allows you to extend validators in the core.
|
||||
2. Import the `AdminPostProductsReq` class from `@medusajs/medusa` as `MedusaAdminPostProductsReq` since this guide extends the Create Product API Route validator. If you're extending a different validator, make sure to import it instead.
|
||||
3. Create a class `AdminPostProductsReq` that extends `MedusaAdminPostProductsReq` and adds a new field `custom_field`. Notice that the name of the class must be the same name of the validator defined in the core. `custom_field` has the type `string`. You can change the type or name of the field, or add more fields.
|
||||
4. Call `registerOverriddenValidators` passing it the `AdminPostProductsReq` class you created. This will override the validator defined in the core to include the new field `custom_field` among the existing fields defined in the core.
|
||||
|
||||
:::tip
|
||||
|
||||
Validators are defined in the same file as the API Route. To find the validator you need to override, find the API Route file under `@medusajs/medusa/dist/api/routes` and import the validator in that file.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Test it Out
|
||||
|
||||
To test out your extended validator, build and start your Medusa backend:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
Then, send a request to the API Route you extended passing it your custom fields. To test out the example in this guide, send an [authenticated request](https://docs.medusajs.com/api/admin#authentication) to the [Create Product API Route](https://docs.medusajs.com/api/admin#products_postproducts) and pass it the `custom_field` body parameter. The request should execute with no errors.
|
||||
@@ -1,68 +0,0 @@
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# API Routes
|
||||
|
||||
In this document, you’ll learn what API Routes are in Medusa.
|
||||
|
||||
## Introduction
|
||||
|
||||
The Medusa Backend is a Node.js headless server. It exposes commerce functionalities implemented by Medusa's [commerce module](../../modules/overview.mdx) through REST APIs that external or frontend applications can access to process or retrieve data.
|
||||
|
||||
The backend exposes two types of API routes: Store APIs and Admin APIs. The Store APIs are typically accessed from the storefront. For example, you can use the Store APIs to show customers available products or implement a cart and checkout flow.
|
||||
|
||||
The Admin APIs are typically accessed from an admin dashboard. For example, you can use the Admin APIs to allow admins to manage the store’s data such as products, orders, and so on.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: 'https://docs.medusajs.com/api/store',
|
||||
label: 'Store APIs',
|
||||
customProps: {
|
||||
icon: Icons['server-solid'],
|
||||
description: 'Check out available Store REST APIs.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: 'https://docs.medusajs.com/api/admin',
|
||||
label: 'Admin APIs',
|
||||
customProps: {
|
||||
icon: Icons['server-solid'],
|
||||
description: 'Check out available Admin REST APIs.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Custom Development
|
||||
|
||||
Aside from using the Medusa backend's API routes, developers can create their own API routes either directly in the Medusa Backend or in a plugin.
|
||||
|
||||
:::tip
|
||||
|
||||
As the core Medusa package is completely customizable, developers can also extend the functionality even further to implement GraphQL routes.
|
||||
|
||||
:::
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/api-routes/create',
|
||||
label: 'Create an API Route',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create an API Route in Medusa.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/api-routes/add-middleware',
|
||||
label: 'Add a Middleware',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to add a middleware in Medusa.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
@@ -1,177 +0,0 @@
|
||||
---
|
||||
description: "In this document, you’ll learn about the directory structure of a Medusa backend. It’ll help you understand the purpose of each file and folder in your Medusa backend project."
|
||||
---
|
||||
|
||||
# Medusa Backend Directory Structure
|
||||
|
||||
In this document, you’ll learn about the directory structure of a Medusa backend. It’ll help you understand the purpose of each file and folder in your Medusa backend project.
|
||||
|
||||
---
|
||||
|
||||
## Root Files
|
||||
|
||||
These are files present at the root of your Medusa backend.
|
||||
|
||||
### .babelrc.js
|
||||
|
||||
Defines Babel’s configurations, which are used when running the `build` command that transpiles files from the `src` directory to the `dist` directory.
|
||||
|
||||
### .env
|
||||
|
||||
Includes the values of environment variables. This is typically only used in development. In production you should define environment variables based on your hosting provider.
|
||||
|
||||
### .env.template
|
||||
|
||||
Gives an example of what variables may be included in `.env`.
|
||||
|
||||
### .gitignore
|
||||
|
||||
Specifies files that shouldn’t be committed to a Git repository.
|
||||
|
||||
### .yarnrc.yml
|
||||
|
||||
Ensures dependencies are always installed in `node-modules`. This ensures compatibility with pnpm.
|
||||
|
||||
### index.js
|
||||
|
||||
Defines an entry file, which is useful when starting the Medusa backend with a process manager like pm2.
|
||||
|
||||
### medusa-config.js
|
||||
|
||||
Defines the Medusa backend’s configurations, including the database configurations, plugins used, modules used, and more.
|
||||
|
||||
**Read more:** [Medusa backend configurations](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx).
|
||||
|
||||
### package.json
|
||||
|
||||
Since the Medusa backend is an NPM package, this file defines its information as well as its dependencies. It will also include any new dependencies you install.
|
||||
|
||||
### README.md
|
||||
|
||||
Provides general information about the Medusa backend.
|
||||
|
||||
### tsconfig.admin.json
|
||||
|
||||
Defines the TypeScript configurations that are used to transpile admin customization files. So, it only works for files under the [src/admin directory](#admin).
|
||||
|
||||
### tsconfig.json
|
||||
|
||||
Defines the general TypeScript configurations used to transpile files from the `src` directory to the `dist` directory.
|
||||
|
||||
### tsconfig.server.json
|
||||
|
||||
Defines the TypeScript configurations that are used to transpile Medusa backend customization files. It works for all files except for the files under the `src/admin` directory.
|
||||
|
||||
### tsconfig.spec.json
|
||||
|
||||
Defines TypeScript configurations for test files. These are files that either reside under a `__tests__` directory under `src`, or that have a file name ending with one of the following:
|
||||
|
||||
- `.test.ts` or `.test.js`
|
||||
- `.spec.ts` or `.test.js`
|
||||
|
||||
### yarn.lock or package-lock.json
|
||||
|
||||
An automatically generated file by `yarn` or `npm` that holds the current versions of all dependencies installed to ensure the correct versions are always installed.
|
||||
|
||||
If you used the `create-medusa-app` command to install the Medusa backend, it’ll attempt to use `yarn` by default to install the dependencies. If `yarn` is not installed on your machine, it will then fall back to using `npm`.
|
||||
|
||||
Based on the package manager used to install the dependencies, either `yarn.lock` or `package-lock.json` will be available, or both.
|
||||
|
||||
---
|
||||
|
||||
## Root Directories
|
||||
|
||||
These are the directories present at the root of your Medusa backend.
|
||||
|
||||
### .cache
|
||||
|
||||
This directory will only be available if you have the Medusa Admin installed and you’ve already started your Medusa backend at least once before. It holds all cached files related to building the Medusa Admin assets.
|
||||
|
||||
### build
|
||||
|
||||
This directory will only be available if you have the Medusa Admin installed and you’ve either built your admin files or ran the Medusa backend at least once before. It holds the built files that are used to serve the admin in your browser.
|
||||
|
||||
### data
|
||||
|
||||
This directory holds a JSON file used to seed your Medusa backend with dummy data which can be useful for demo purposes. The data is seeded automatically if you include the `--seed` option when using either the `create-medusa-app` or `medusa new` commands.
|
||||
|
||||
You can also seed the data by running the following command:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run seed
|
||||
```
|
||||
|
||||
### dist
|
||||
|
||||
This directory holds the transpiled Medusa backend customizations. This directory may not be available when you first install the Medusa backend. It’ll be available when you run the `build` command or start your Medusa backend with the `dev` command.
|
||||
|
||||
The files under this directory are the files that are used in your Medusa backend. So, when you make any changes under `src`, make sure the changes are transpiled into the `dist` directory. If you’re using the `dev` or `medusa develop` commands, this is handled automatically whenever changes occur under the `src` directory.
|
||||
|
||||
### node_modules
|
||||
|
||||
This directory holds all installed dependencies in your project.
|
||||
|
||||
### src
|
||||
|
||||
This directory holds all Medusa backend and admin customizations. More details about each subdirectory are included in [this section](#src-subdirectories).
|
||||
|
||||
### uploads
|
||||
|
||||
This directory holds all file uploads to the Medusa backend. It’s only used if you’re using the [Local File Service plugin](../../plugins/file-service/local.md), which is installed by default.
|
||||
|
||||
---
|
||||
|
||||
## src Subdirectories
|
||||
|
||||
Files under the `src` directory hold the Medusa backend and admin customizations. These files should later be transpiled into the `dist` directory for them to be used during the backend’s runtime.
|
||||
|
||||
If any of these directories are not available, you can create them yourself.
|
||||
|
||||
### admin
|
||||
|
||||
This directory holds all Medusa Admin customizations. The main subdirectories of this directory are:
|
||||
|
||||
- `widgets`: Holds all [Medusa Admin widgets](../../admin/widgets.md).
|
||||
- `routes`: Holds all [Medusa Admin UI routes](../../admin/routes.md).
|
||||
|
||||
### api
|
||||
|
||||
This directory holds all custom API Routes, which are defined in `route.ts` or `route.js` files. These files can be created in sub-directories of the `api` directory based on the API Route's path.
|
||||
|
||||
**Read more:** [API Routes](../api-routes/overview.mdx)
|
||||
|
||||
### loaders
|
||||
|
||||
This directory holds scripts that run when the Medusa backend starts. For example, the scripts can define a scheduled job.
|
||||
|
||||
**Read more:** [Loaders](../loaders/overview.mdx)
|
||||
|
||||
### migrations
|
||||
|
||||
This directory holds all migration scripts that reflect changes on the database the Medusa backend is connected to.
|
||||
|
||||
**Read more:** [Migrations](../entities/migrations/overview.mdx)
|
||||
|
||||
### models
|
||||
|
||||
This directory holds all custom entities, which represent tables in your database. You can create a new entity, or customize a Medusa entity.
|
||||
|
||||
**Read more:** [Entities](../entities/overview.mdx)
|
||||
|
||||
### repositories
|
||||
|
||||
This directory holds all custom repositories which provide utility methods to access and modify data related to an entity.
|
||||
|
||||
**Read more:** [Repositories](../entities/overview.mdx#what-are-repositories)
|
||||
|
||||
### services
|
||||
|
||||
This directory holds all custom services. Services define utility methods related to an entity or feature that can be used across the Medusa backend’s resources.
|
||||
|
||||
**Read more**: [Services](../services/overview.mdx)
|
||||
|
||||
### subscribers
|
||||
|
||||
This directory holds all custom subscribers. Subscribers listen to emitted events and registers method to handle them.
|
||||
|
||||
**Read more:** [Subscribers](../events/subscribers.mdx)
|
||||
@@ -1,170 +0,0 @@
|
||||
---
|
||||
description: 'This quickstart guide will help you set up a Medusa backend in three steps.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Feedback from '@site/src/components/Feedback';
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import Icons from '@theme/Icon';
|
||||
import DetailsList from '@site/src/components/DetailsList'
|
||||
import SaslSection from '../../troubleshooting/database-errors/_sasl.md'
|
||||
import ConnectionErrorSection from '../../troubleshooting/database-errors/_connection-error.md'
|
||||
import FreshInstallationSection from '../../troubleshooting/awilix-resolution-error/_fresh-installation.md'
|
||||
import EaddrinuseSection from '../../troubleshooting/eaddrinuse.md'
|
||||
import DockerSection from '../../troubleshooting/database-errors/_docker.md'
|
||||
|
||||
# Install Medusa Backend
|
||||
|
||||
This document will guide you through setting up your Medusa backend in a three steps.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before you can install and use Medusa, you need the following tools installed on your machine:
|
||||
|
||||
- [Node.js v16+](./prepare-environment.mdx#nodejs)
|
||||
- [Git](./prepare-environment.mdx#git)
|
||||
- [PostgreSQL](./prepare-environment.mdx#postgresql)
|
||||
|
||||
---
|
||||
|
||||
## Create a Medusa Backend
|
||||
|
||||
:::tip
|
||||
|
||||
It is recommended to use [Yarn](https://yarnpkg.com/getting-started/install) for the installation process as it's much faster than using NPM.
|
||||
|
||||
:::
|
||||
|
||||
### 1. Install Medusa CLI
|
||||
|
||||
To install the Medusa backend, you need Medusa's CLI tool. You can install it globally or, alternatively, use it through npx with `npx @medusajs/medusa-cli <command>`.
|
||||
|
||||
```bash npm2yarn
|
||||
npm install @medusajs/medusa-cli -g
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
If you run into any errors while installing the CLI tool, check out the [troubleshooting guide](../../troubleshooting/cli-installation-errors.mdx).
|
||||
|
||||
:::
|
||||
|
||||
### 2. Create a new Medusa project
|
||||
|
||||
```bash noReport
|
||||
medusa new my-medusa-store # or npx @medusajs/medusa-cli new
|
||||
```
|
||||
|
||||
You'll then be asked to specify your PostgreSQL database credentials. You can choose "Continue" to use the default credentials shown in the terminal, choose "Change credentials" to specify your PostgreSQL credentials, or choose "Skip database setup" to create the database later.
|
||||
|
||||
:::warning
|
||||
|
||||
If you choose "Skip database setup" you will need to [set the database configurations](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx) and [run migrations](../entities/migrations/overview.mdx#migrate-command) later.
|
||||
|
||||
:::
|
||||
|
||||
### 3. Start your Medusa backend
|
||||
|
||||
:::note
|
||||
|
||||
Make sure your PostgreSQL server is running before you run the Medusa backend.
|
||||
|
||||
:::
|
||||
|
||||
```bash noReport
|
||||
cd my-medusa-store
|
||||
medusa develop # or npx medusa develop
|
||||
```
|
||||
|
||||
After these three steps and in only a couple of minutes, you now have a complete commerce engine running locally. You can test it out by sending a request using a tool like Postman or through the command line:
|
||||
|
||||
```bash noReport
|
||||
curl localhost:9000/store/products
|
||||
```
|
||||
|
||||
<Feedback
|
||||
event="survey_server_quickstart"
|
||||
question="Did you set up the backend successfully?"
|
||||
positiveQuestion="Is there anything that should improved?"
|
||||
negativeQuestion="Please describe the issue you faced."
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting Installation
|
||||
|
||||
<DetailsList
|
||||
sections={[
|
||||
{
|
||||
title: 'Error: SASL: SCRAM-SERVER-FIRST-MESSAGE: Client password must be a string',
|
||||
content: <SaslSection />
|
||||
},
|
||||
{
|
||||
title: "Can't Connect to PostgreSQL Docker Container",
|
||||
content: <DockerSection />
|
||||
},
|
||||
{
|
||||
title: 'Error: connect ECONNREFUSED ::1:5432',
|
||||
content: <ConnectionErrorSection />
|
||||
},
|
||||
{
|
||||
title: 'Error: EADDRINUSE',
|
||||
content: <EaddrinuseSection />
|
||||
},
|
||||
{
|
||||
title: 'AwilixResolutionError: Could Not Resolve X',
|
||||
content: <FreshInstallationSection />
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## Seed Data
|
||||
|
||||
For better testing, you can add demo data to your Medusa backend by running the seed command in your Medusa backend directory:
|
||||
|
||||
```bash
|
||||
medusa seed --seed-file=data/seed.json
|
||||
# or npx medusa seed --seed-file=data/seed.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Health Route
|
||||
|
||||
You can access `/health` to get health status of your backend.
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/backend/directory-structure',
|
||||
label: 'Directory Structure',
|
||||
customProps: {
|
||||
icon: Icons['document-text-solid'],
|
||||
description: 'Learn about the purpose of each file and directory in the Medusa backend.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/backend/configurations',
|
||||
label: 'Backend Configurations',
|
||||
customProps: {
|
||||
icon: Icons['tools-solid'],
|
||||
description: 'Learn about configuring your backend and loading environment variables.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/fundamentals/architecture-overview',
|
||||
label: 'Backend Architecture',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the different resources that your Medusa backend is made of.'
|
||||
}
|
||||
}
|
||||
]} />
|
||||
@@ -1,159 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to prepare your development environment while using Medusa. This guide includes how to install Node.js, Git, Medusa CLI tool, and PostgreSQL.'
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import MedusaCliTroubleshootingSection from '../../troubleshooting/cli-installation-errors/_reusable-section.mdx'
|
||||
|
||||
# Prepare Development Environment
|
||||
|
||||
This document includes the installation instructions for the tools required to use and run Medusa.
|
||||
|
||||
## Node.js
|
||||
|
||||
Node.js is the environment that makes it possible for Medusa to run, so you must install Node.js on your machine to start Medusa development.
|
||||
|
||||
:::warning
|
||||
|
||||
Medusa requires version 16.14.0 or higher of Node. You can check your Node version using the following command:
|
||||
|
||||
```bash noReport
|
||||
node -v
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
<Tabs groupId="operating-systems" queryString="os">
|
||||
<TabItem value="windows" label="Windows" default>
|
||||
You can install the executable directly from [the Node.js website](https://nodejs.org/en/#home-downloadhead).
|
||||
|
||||
For other approaches, you can check out [Node.js’s guide](https://nodejs.org/en/download/package-manager/#windows-1).
|
||||
</TabItem>
|
||||
<TabItem value="linux" label="Linux">
|
||||
You can use the following commands to install Node.js on Ubuntu:
|
||||
|
||||
```bash
|
||||
#Ubuntu
|
||||
sudo apt update
|
||||
sudo apt install nodejs
|
||||
```
|
||||
|
||||
For other Linux distributions, you can check out [Node.js’s guide](https://nodejs.org/en/download/package-manager/).
|
||||
</TabItem>
|
||||
<TabItem value="macos" label="macOS">
|
||||
You can use the following commands to install Node.js on macOS:
|
||||
|
||||
<Tabs groupId="homebrew" isCodeTabs={true}>
|
||||
<TabItem value="homebrew" label="Homebrew">
|
||||
```bash
|
||||
brew install node
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="no-homebrew" label="Without Homebrew">
|
||||
```bash
|
||||
curl \
|
||||
"https://nodejs.org/dist/latest/node-${VERSION:-$(wget -qO- \
|
||||
https://nodejs.org/dist/latest/ | sed -nE \
|
||||
's|.*>node-(.*)\.pkg</a>.*|\1|p')}.pkg" \
|
||||
> "$HOME/Downloads/node-latest.pkg" &&
|
||||
sudo installer -store -pkg "$HOME/Downloads/node-latest.pkg" -target "/"
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
For other approaches, you can check out [Node.js’s guide](https://nodejs.org/en/download/package-manager/#macos).
|
||||
|
||||
Make sure that you have Xcode command line tools installed. If not, run the following command to install it: `xcode-select --install`.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
---
|
||||
|
||||
## Git
|
||||
|
||||
Medusa uses Git behind the scenes when you create a new project. So, you'll have to install it on your machine to get started.
|
||||
|
||||
<Tabs groupId="operating-systems" queryString="os">
|
||||
<TabItem value="windows" label="Windows" default>
|
||||
To install Git on Windows, you need to [download the installable package](https://git-scm.com/download/win).
|
||||
</TabItem>
|
||||
<TabItem value="linux" label="Linux">
|
||||
For Debian/Ubuntu, you can use the following command:
|
||||
|
||||
```bash
|
||||
apt-get install git
|
||||
```
|
||||
|
||||
As for other Linux distributions, please check [git’s guide](https://git-scm.com/download/linux).
|
||||
</TabItem>
|
||||
<TabItem value="macos" label="macOS">
|
||||
You should already have Git installed as part of the Xcode command-line tools.
|
||||
|
||||
However, if for any reason you need to install it manually, you can install it with Homebrew:
|
||||
|
||||
```bash
|
||||
brew install git
|
||||
```
|
||||
|
||||
You can also check out [git’s guide](https://git-scm.com/download/mac) for more installation options.
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
---
|
||||
|
||||
## PostgreSQL
|
||||
|
||||
The Medusa backend uses PostgreSQL to store data of your commerce system.
|
||||
|
||||
<Tabs groupId="operating-systems" queryString="os">
|
||||
<TabItem value="windows" label="Windows">
|
||||
You can [download the PostgreSQL Windows installer](https://www.postgresql.org/download/windows/) from their website.
|
||||
</TabItem>
|
||||
<TabItem value="linux" label="Linux">
|
||||
If you’re using Ubuntu, you can use the following commands to download and install PostgreSQL:
|
||||
|
||||
```bash
|
||||
sudo sh -c \
|
||||
'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
|
||||
wget --quiet -O - \
|
||||
https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install postgresql
|
||||
```
|
||||
|
||||
For other distributions, you can check out [PostgreSQL’s website for more guides](https://www.postgresql.org/download/linux/).
|
||||
</TabItem>
|
||||
<TabItem value="macos" label="macOS">
|
||||
You can download PostgreSQL on your macOS using [the installer on their website](https://www.postgresql.org/download/macosx/).
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
---
|
||||
|
||||
## (Optional) Medusa CLI
|
||||
|
||||
Medusa provides a CLI tool that can aid your through your Medusa development. You can install it globally, or you can use it through `npx`.
|
||||
|
||||
You can install Medusa’s CLI with the following command:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install @medusajs/medusa-cli -g
|
||||
```
|
||||
|
||||
To confirm that the CLI tool was installed successfully, run the following command:
|
||||
|
||||
```bash noReport
|
||||
medusa -v
|
||||
```
|
||||
|
||||
### Troubleshooting Installation
|
||||
|
||||
<MedusaCliTroubleshootingSection />
|
||||
|
||||
---
|
||||
|
||||
## Install Medusa Backend
|
||||
|
||||
Once you're done installing the necessary tools in your environment, check out the [Medusa Backend Quickstart](./install.mdx) to install your Medusa backend.
|
||||
@@ -1,529 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to create a batch job strategy in Medusa. This guide also includes how to test your batch job strategy.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# Create a Batch Job Strategy
|
||||
|
||||
In this document, you’ll learn how to create a batch job strategy in Medusa.
|
||||
|
||||
:::info
|
||||
|
||||
If you’re interested to learn more about what Batch Jobs are and how they work, check out [this documentation](./index.mdx).
|
||||
|
||||
:::
|
||||
|
||||
## Overview
|
||||
|
||||
Batch jobs can be used to perform long tasks in the background of your Medusa backend. Batch jobs are handled by batch job strategies. An example of a batch job strategy is the Import Products functionality.
|
||||
|
||||
This documentation helps you learn how to create a batch job strategy. The batch job strategy used in this example changes the status of all draft products to `published`.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Components
|
||||
|
||||
It is assumed that you already have a Medusa backend installed and set up. If not, you can follow our [quickstart guide](../backend/install.mdx) to get started. The Medusa backend must also have an event bus module installed, which is available when using the default Medusa backend starter.
|
||||
|
||||
---
|
||||
|
||||
## 1. Create a File
|
||||
|
||||
A batch job strategy is essentially a class defined in a TypeScript or JavaScript file. You should create this file in `src/strategies`.
|
||||
|
||||
Following the example used in this documentation, create the file `src/strategies/publish.ts`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Create Class
|
||||
|
||||
Batch job strategies must extend the abstract class `AbstractBatchJobStrategy` and implement its abstract methods.
|
||||
|
||||
Add the following content to the file you created:
|
||||
|
||||
```ts title="src/strategies/publish.ts"
|
||||
import {
|
||||
AbstractBatchJobStrategy,
|
||||
BatchJobService,
|
||||
} from "@medusajs/medusa"
|
||||
import { EntityManager } from "typeorm"
|
||||
|
||||
class PublishStrategy extends AbstractBatchJobStrategy {
|
||||
protected batchJobService_: BatchJobService
|
||||
processJob(batchJobId: string): Promise<void> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
buildTemplate(): Promise<string> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
protected manager_: EntityManager
|
||||
protected transactionManager_: EntityManager
|
||||
|
||||
}
|
||||
|
||||
export default PublishStrategy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Define Required Properties
|
||||
|
||||
A batch job strategy class must have two static properties: the `identifier` and `batchType` properties. The `identifier` must be a unique string associated with your batch job strategy, and `batchType` must be the batch job's type.
|
||||
|
||||
You will use the `batchType` later when you [interact with the Batch Job APIs](#test-your-batch-job-strategy).
|
||||
|
||||
Following the same example, add the following properties to the `PublishStrategy` class:
|
||||
|
||||
```ts
|
||||
class PublishStrategy extends AbstractBatchJobStrategy {
|
||||
static identifier = "publish-products-strategy"
|
||||
static batchType = "publish-products"
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Define Methods
|
||||
|
||||
### (Optional) prepareBatchJobForProcessing
|
||||
|
||||
Medusa runs this method before it creates the batch job to prepare the content of the batch job record in the database. It accepts two parameters: the batch job data sent in the body of the [Create Batch Job request](https://docs.medusajs.com/api/admin#batch-jobs_postbatchjobs), and the request instance.
|
||||
|
||||
Implementing this method is optional. For example:
|
||||
|
||||
```ts
|
||||
class PublishStrategy extends AbstractBatchJobStrategy {
|
||||
// ...
|
||||
async prepareBatchJobForProcessing(
|
||||
batchJob: CreateBatchJobInput,
|
||||
req: Express.Request
|
||||
): Promise<CreateBatchJobInput> {
|
||||
// make changes to the batch job's fields...
|
||||
return batchJob
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### (Optional) preProcessBatchJob
|
||||
|
||||
Medusa runs this method after it creates the batch job, but before it is confirmed and processed. You can use this method to perform any necessary action before the batch job is processed. You can also use this method to add information related to the expected result.
|
||||
|
||||
For example, this implementation of the `preProcessBatchJob` method calculates how many draft products it will published and adds it to the `result` attribute of the batch job:
|
||||
|
||||
```ts
|
||||
class PublishStrategy extends AbstractBatchJobStrategy {
|
||||
// ...
|
||||
async preProcessBatchJob(batchJobId: string): Promise<void> {
|
||||
return await this.atomicPhase_(
|
||||
async (transactionManager) => {
|
||||
const batchJob = (await this.batchJobService_
|
||||
.withTransaction(transactionManager)
|
||||
.retrieve(batchJobId))
|
||||
|
||||
const count = await this.productService_
|
||||
.withTransaction(transactionManager)
|
||||
.count({
|
||||
status: ProductStatus.DRAFT,
|
||||
})
|
||||
|
||||
await this.batchJobService_
|
||||
.withTransaction(transactionManager)
|
||||
.update(batchJob, {
|
||||
result: {
|
||||
advancement_count: 0,
|
||||
count,
|
||||
stat_descriptors: [
|
||||
{
|
||||
key: "product-publish-count",
|
||||
name: "Number of products to publish",
|
||||
message:
|
||||
`${count} product(s) will be published.`,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `result` attribute is an object that can hold many properties including:
|
||||
|
||||
- `count`: used to indicate how many items (in this case, products) that the task will run on.
|
||||
- `advancement_count`: used to indicate the current number of items processed at a given moment. Since the batch job isn't processed yet, you set it to zero.
|
||||
- `stat_descriptors`: can be used to show human-readable messages.
|
||||
|
||||
### processJob
|
||||
|
||||
Medusa runs this method to process the batch job once it is confirmed.
|
||||
|
||||
For example, this implementation of the `processJob` method retrieves all draft products and changes their status to published:
|
||||
|
||||
```ts
|
||||
class PublishStrategy extends AbstractBatchJobStrategy {
|
||||
// ...
|
||||
async processJob(batchJobId: string): Promise<void> {
|
||||
return await this.atomicPhase_(
|
||||
async (transactionManager) => {
|
||||
const productServiceTx = this.productService_
|
||||
.withTransaction(transactionManager)
|
||||
|
||||
const productList = await productServiceTx
|
||||
.list({
|
||||
status: [ProductStatus.DRAFT],
|
||||
})
|
||||
|
||||
productList.forEach(async (product: Product) => {
|
||||
await productServiceTx
|
||||
.update(product.id, {
|
||||
status: ProductStatus.PUBLISHED,
|
||||
})
|
||||
})
|
||||
|
||||
await this.batchJobService_
|
||||
.withTransaction(transactionManager)
|
||||
.update(batchJobId, {
|
||||
result: {
|
||||
advancement_count: productList.length,
|
||||
},
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
When a batch job is canceled, the processing of the batch job doesn’t automatically stop. You will have to manually check for changes in the status of the batch job. For example, you can retrieve the batch job and use the condition `batchJob.status === BatchJobStatus.CANCELED` to check if the batch job was canceled.
|
||||
|
||||
:::
|
||||
|
||||
### buildTemplate
|
||||
|
||||
This method can be used in cases where you provide a template file to download, such as when implementing an import or export functionality.
|
||||
|
||||
If not necessary to your use case, you can simply return an empty string:
|
||||
|
||||
```ts
|
||||
class PublishStrategy extends AbstractBatchJobStrategy {
|
||||
// ...
|
||||
async buildTemplate(): Promise<string> {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### (Optional) shouldRetryOnProcessingError
|
||||
|
||||
Medusa uses this method to decide whether it should retry the batch job if an error occurs during processing.
|
||||
|
||||
By default, the `AbstractBatchJobStrategy` class implements this method and returns `false`, indicating that if a batch job’s process fails it will not be retried.
|
||||
|
||||
If you would like to change that behavior, you can override this method to return a different value:
|
||||
|
||||
```ts
|
||||
class PublishStrategy extends AbstractBatchJobStrategy {
|
||||
// ...
|
||||
protected async shouldRetryOnProcessingError(
|
||||
batchJob: BatchJob,
|
||||
err: unknown
|
||||
): Promise<boolean> {
|
||||
return true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### (Optional) handleProcessingError
|
||||
|
||||
Medusa uses this method to handle errors that occur during processing. By default, it changes the status of the batch job to `failed` and sets the `errors` property of the batch job’s `result` attribute.
|
||||
|
||||
You can use this method as implemented in `AbstractBatchJobStrategy` at any point in your batch job process to set the batch job as failed.
|
||||
|
||||
You can also override this method in your batch job strategy and change how it works:
|
||||
|
||||
```ts
|
||||
class PublishStrategy extends AbstractBatchJobStrategy {
|
||||
// ...
|
||||
protected async handleProcessingError<T>(
|
||||
batchJobId: string,
|
||||
err: unknown,
|
||||
result: T
|
||||
): Promise<void> {
|
||||
// different implementation...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Run Build Command
|
||||
|
||||
After you create the batch job and before testing it out, you must run the build command in the directory of your Medusa backend:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test your Batch Job Strategy
|
||||
|
||||
This section covers how to test and use your batch job strategy. Make sure to start your backend first:
|
||||
|
||||
```bash
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
You must also use an authenticated user to send batch job requests. You can refer to the [authentication guide in the API reference](https://docs.medusajs.com/api/admin#authentication) for more details.
|
||||
|
||||
If you follow along with the JS Client code snippets, make sure to [install and set it up first](../../js-client/overview.mdx).
|
||||
|
||||
### Create Batch Job
|
||||
|
||||
The first step is to create a batch job using the [Create Batch Job API Route](https://docs.medusajs.com/api/admin#batch-jobs_postbatchjobs). In the body of the request, you must set the `type` to the value of `batchType` in the batch job strategy you created.
|
||||
|
||||
For example, this creates a batch job of the type `publish-products`:
|
||||
|
||||
<Tabs groupId="request-types" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.batchJobs.create({
|
||||
type: "publish-products",
|
||||
context: { },
|
||||
dry_run: true,
|
||||
})
|
||||
.then(( batch_job ) => {
|
||||
console.log(batch_job.status)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCreateBatchJob } from "medusa-react"
|
||||
|
||||
const CreateBatchJob = () => {
|
||||
const createBatchJob = useAdminCreateBatchJob()
|
||||
// ...
|
||||
|
||||
const handleCreateBatchJob = () => {
|
||||
createBatchJob.mutate({
|
||||
type: "publish-products",
|
||||
context: {},
|
||||
dry_run: true
|
||||
}, {
|
||||
onSuccess: ({ batch_job }) => {
|
||||
console.log(batch_job)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CreateBatchJob
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/batch-jobs`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: "publish-products",
|
||||
context: { },
|
||||
dry_run: true,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ batch_job }) => {
|
||||
console.log(batch_job.status)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/batch-jobs' \
|
||||
-H 'x-medusa-access-token: <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"type": "publish-products",
|
||||
"context": { },
|
||||
"dry_run": true
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
You set the `dry_run` to `true` to disable automatic confirmation and running of the batch job. If you want the batch job to run automatically, you can remove this body parameter.
|
||||
|
||||
Make sure to replace `<BACKEND_URL>` with the backend URL where applicable.
|
||||
|
||||
### (Optional) Retrieve Batch Job
|
||||
|
||||
You can retrieve the batch job afterward to get its status and view details about the process in the `result` property:
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.batchJobs.retrieve(batchJobId)
|
||||
.then(( batch_job ) => {
|
||||
console.log(batch_job.status, batch_job.result)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminBatchJob } from "medusa-react"
|
||||
|
||||
type Props = {
|
||||
batchJobId: string
|
||||
}
|
||||
|
||||
const BatchJob = ({ batchJobId }: Props) => {
|
||||
const { batch_job, isLoading } = useAdminBatchJob(batchJobId)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{batch_job && <span>{batch_job.created_by}</span>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default BatchJob
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/batch-jobs/${batchJobId}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ batch_job }) => {
|
||||
console.log(batch_job.status, batch_job.result)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/batch-jobs/<BATCH_JOB_ID>' \
|
||||
-H 'x-medusa-access-token: <API_TOKEN>'
|
||||
# <BATCH_JOB_ID> is the ID of the batch job
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Based on the batch job strategy implemented in this documentation, the `result` property could be something like this:
|
||||
|
||||
```json noReport
|
||||
"result": {
|
||||
"count": 1,
|
||||
"stat_descriptors": [
|
||||
{
|
||||
"key": "product-publish-count",
|
||||
"name": "Number of products to publish",
|
||||
"message": "1 product(s) will be published."
|
||||
}
|
||||
],
|
||||
"advancement_count": 0
|
||||
},
|
||||
```
|
||||
|
||||
### Confirm Batch Job
|
||||
|
||||
To process the batch job, send a request to [confirm the batch job](https://docs.medusajs.com/api/admin#batch-jobs_postbatchjobsbatchjobconfirmprocessing):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.batchJobs.confirm(batchJobId)
|
||||
.then(( batch_job ) => {
|
||||
console.log(batch_job.status)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminConfirmBatchJob } from "medusa-react"
|
||||
|
||||
type Props = {
|
||||
batchJobId: string
|
||||
}
|
||||
|
||||
const BatchJob = ({ batchJobId }: Props) => {
|
||||
const confirmBatchJob = useAdminConfirmBatchJob(batchJobId)
|
||||
// ...
|
||||
|
||||
const handleConfirm = () => {
|
||||
confirmBatchJob.mutate(undefined, {
|
||||
onSuccess: ({ batch_job }) => {
|
||||
console.log(batch_job)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default BatchJob
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/batch-jobs/${batchJobId}/confirm`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ batch_job }) => {
|
||||
console.log(batch_job.status)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/batch-jobs/<BATCH_JOB_ID>/confirm' \
|
||||
-H 'x-medusa-access-token: <API_TOKEN>'
|
||||
# <BATCH_JOB_ID> is the ID of the batch job
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The batch job will start processing afterward. Based on the batch job strategy implemented in this documentation, draft products will be published.
|
||||
|
||||
You can [retrieve the batch job](#optional-retrieve-batch-job) at any given point to check its status.
|
||||
@@ -1,122 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to customize the import strategy in Medusa. The import strategy can be used to import entities such as products, prices in a price list, orders, or other entities.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Customize Import Strategy
|
||||
|
||||
In this document, you’ll learn how to create a custom product import strategy either by overriding the default strategy or creating your own.
|
||||
|
||||
## Overview
|
||||
|
||||
Product Import Strategy is essentially a batch job strategy. Medusa provides the necessary mechanisms to override or create your own strategy.
|
||||
|
||||
Although this documentation specifically targets import strategies, you can use the same steps to override any batch job strategy in Medusa, including export strategies.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Components
|
||||
|
||||
It's assumed that you already have a Medusa backend installed and set up. If not, you can follow our [quickstart guide](../backend/install.mdx) to get started. The Medusa backend must also have an event bus module installed, which is available when using the default Medusa backend starter.
|
||||
|
||||
---
|
||||
|
||||
## Override Batch Job Strategy
|
||||
|
||||
The steps required for overriding a batch job strategy are essentially the same steps required to create a batch job strategy with a minor difference. For that reason, this documentation does not cover the basics of a batch job strategy.
|
||||
|
||||
If you’re interested to learn more about batch job strategies and how they work, please check out the [Create Batch Job Strategy documentation](./create.mdx).
|
||||
|
||||
### 1. Create a File
|
||||
|
||||
You must store batch job strategies in the `src/strategies` directory of your Medusa backend. They are either TypeScript or JavaScript files.
|
||||
|
||||
So, for example, you can create the file `src/strategies/import.ts`.
|
||||
|
||||
### 2. Create a Class
|
||||
|
||||
The batch job strategy class must extend the `AbstractBatchJobStrategy` class which you can import from Medusa’s core repository.
|
||||
|
||||
For example, you can define the following class in the file you created:
|
||||
|
||||
```ts title="src/strategies/import.ts"
|
||||
import {
|
||||
AbstractBatchJobStrategy,
|
||||
BatchJobService,
|
||||
} from "@medusajs/medusa"
|
||||
import { EntityManager } from "typeorm"
|
||||
|
||||
class MyImportStrategy extends AbstractBatchJobStrategy {
|
||||
protected batchJobService_: BatchJobService
|
||||
protected manager_: EntityManager
|
||||
protected transactionManager_: EntityManager
|
||||
|
||||
processJob(batchJobId: string): Promise<void> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
buildTemplate(): Promise<string> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
}
|
||||
|
||||
export default MyImportStrategy
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
This is the base implementation of a batch job strategy. You can learn about all the different methods and properties in [this documentation](./create.mdx#3-define-required-properties).
|
||||
|
||||
:::
|
||||
|
||||
### 3. Set the batchType Property
|
||||
|
||||
Every batch job strategy class must have the static property `batchType` defined. It determines the type of batch job this strategy handles.
|
||||
|
||||
Since only one batch job strategy can handle a batch job type, you can override Medusa’s default batch job strategies by using the same `batchType` value in your custom strategy.
|
||||
|
||||
So, for example, to override the product import strategy set the `batchType` property in your strategy to `product-import`:
|
||||
|
||||
```ts
|
||||
class MyImportStrategy extends AbstractBatchJobStrategy {
|
||||
static batchType = "product-import"
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Define your Custom Functionality
|
||||
|
||||
You can now define your custom functionality in your batch job strategy. For example, you can create custom import logic to import products.
|
||||
|
||||
Refer to the [Create a Batch Job documentation](./create.mdx#3-define-required-properties) to understand what properties and methods are required in your batch job strategy and how you can use them to implement your custom functionality.
|
||||
|
||||
### 5. Run Build Command
|
||||
|
||||
Before you can test out your batch job strategy, you must run the `build` command:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
### 6. Test your Functionality
|
||||
|
||||
Since you didn’t create a new batch job type and overwrote the functionality of the strategy, you can test out your functionality using the [same steps used with the default strategy](./create.mdx#test-your-batch-job-strategy).
|
||||
|
||||
Specifically, since you create batch jobs using the [Create Batch Job API Route](https://docs.medusajs.com/api/admin#batch-jobs_postbatchjobs) which accepts the batch job type as a body parameter, you just need to send the same type you used for this field. In the example of this documentation, the `type` would be `product-import`.
|
||||
|
||||
If you overwrote the import functionality, you can follow [these steps to learn how to import products using the Admin APIs](../../modules/products/admin/import-products.mdx).
|
||||
|
||||
---
|
||||
|
||||
## Create Custom Batch Job Strategy
|
||||
|
||||
If you don’t want to override Medusa’s batch job strategy, you can create a custom batch job strategy with a different `batchType` value. Then, use that type when you send a request to [Create a Batch Job](https://docs.medusajs.com/api/admin#batch-jobs_postbatchjobs).
|
||||
|
||||
For more details on creating custom batch job strategies, please check out the [Create Batch Job Strategy documentation](./create.mdx).
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Use the Import Product APIs](../../modules/products/admin/import-products.mdx).
|
||||
@@ -1,95 +0,0 @@
|
||||
---
|
||||
description: 'Learn what batch jobs in Medusa are and their flow. Batch jobs are tasks that can be performed asynchronously and iteratively in Medusa.'
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Batch Jobs
|
||||
|
||||
In this document, you’ll learn what Batch Jobs are and how they work in Medusa.
|
||||
|
||||
## What are Batch Jobs
|
||||
|
||||
Batch Jobs are tasks that can be performed asynchronously and iteratively. They can be [created using the Admin API](https://docs.medusajs.com/api/admin#batch-jobs_postbatchjobs), then, once confirmed, they are processed asynchronously.
|
||||
|
||||
Every status change of a batch job triggers an event that can be handled using [subscribers](../events/subscribers.mdx).
|
||||
|
||||
Medusa uses batch jobs in its core to perform some asynchronous tasks. For example, the Export Products functionality uses batch jobs.
|
||||
|
||||
You can also create a custom batch job or overwrite Medusa’s batch jobs.
|
||||
|
||||
### BatchJob Entity Overview
|
||||
|
||||
A batch job is stored in the database as a [BatchJob](../../references/entities/classes/entities.BatchJob.mdx) entity. Some of its important attributes are:
|
||||
|
||||
- `status`: A string that determines the current status of the Batch Job.
|
||||
- `context`: An object that can be used to store configurations related to the batch job. For example, you can use it to store listing configurations such as the limit or offset of the list to be retrieved during the processing of the batch job.
|
||||
- `dry_run`: A boolean value that indicates whether this batch job should actually produce an output. By default it’s false, meaning that by default it produces an output. It can be used to simulate a batch job.
|
||||
- `type`: A string that is used to later resolve the batch job strategy that should be used to handle the batch job.
|
||||
- `result`: An object that includes important properties related to the result of the batch job. Some of these properties are:
|
||||
- `errors`: An error object that contains any errors that occur during the batch job’s execution.
|
||||
- `progress`: A numeric value indicating the progress of the batch job.
|
||||
- `count`: A number that includes the total count of records related to the operation. For example, in the case of product exports, it is used to indicate the total number of products exported.
|
||||
- `advancement_count`: A number that indicates the number of records processed so far. Can be helpful when retrying a batch job.
|
||||
|
||||
---
|
||||
|
||||
## What are Batch Job Strategies
|
||||
|
||||
Batch jobs are handled by batch job strategies. A batch job strategy is a class that extends the `AbstractBatchJobStrategy` abstract class and implements the methods defined in that class to handle the different states of a batch job.
|
||||
|
||||
A batch job strategy must implement the necessary methods to handle the preparation of a batch job before it is created, the preparation of the processing of the batch job after it is created, and the processing of the batch job once it is confirmed.
|
||||
|
||||
When you create a batch job strategy, the `batchType` class property indicates the batch job types this strategy handles. Then, when you create a new batch job, you set the batch job’s type to the value of `batchType` in your strategy.
|
||||
|
||||
---
|
||||
|
||||
## How Batch Jobs Work
|
||||
|
||||
A batch job’s flow from creation to completion is:
|
||||
|
||||
1. A batch job is created using the [Create Batch Job API route](https://docs.medusajs.com/api/admin#batch-jobs_postbatchjobs).
|
||||
2. Once the batch job is created, the batch job’s status is changed to `created` and the `batch.created` event is triggered by the `BatchJobService`.
|
||||
3. The `BatchJobSubscriber` handles the `created` event. It resolves the batch job strategy based on the `type` of the batch job, then uses it to pre-process the batch job. After this, the batch job’s status is changed to `pre_processed`. Only when the batch job has the status `pre_processed` can be confirmed.
|
||||
4. If `dry_run` is not set in the Create Batch Job request in step one or if it is set to `false`, the batch job will automatically be confirmed after processing. Otherwise, if `dry_run` is set to `true`, the batch job can be confirmed using the [Confirm Batch Job API Route](https://docs.medusajs.com/api/admin#batch-jobs_postbatchjobsbatchjobconfirmprocessing).
|
||||
5. Once the batch job is confirmed, the batch job’s status is changed to `confirmed` and the `batch.confirmed` event is triggered by the `BatchJobService`.
|
||||
6. The `BatchJobSubscriber` handles the `confirmed` event. It resolves the batch job strategy, then uses it to process the batch job.
|
||||
7. Once the batch job is processed successfully, the batch job has the status `completed`.
|
||||
|
||||
You can track the progress of the batch job at any point using the [Retrieve Batch Job API Route](https://docs.medusajs.com/api/admin#batch-jobs_getbatchjobsbatchjob).
|
||||
|
||||
:::info
|
||||
|
||||
If the batch job fails at any point in this flow, its status is changed to `failed`.
|
||||
|
||||
:::
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Custom Development
|
||||
|
||||
Developers can create custom batch jobs in the Medusa backend, a plugin, or in a module. Developers can also customize existing batch job strategies in the core, such as the import strategy.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/batch-jobs/create',
|
||||
label: 'Create a Batch Job Strategy',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a batch job strategy in Medusa.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/batch-jobs/customize-import',
|
||||
label: 'Customize Import Strategy',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to customize the import strategy in Medusa.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
-267
@@ -1,267 +0,0 @@
|
||||
---
|
||||
description: 'In this document, you’ll learn about how to create a cache module in Medusa, using Memcached as an example.'
|
||||
---
|
||||
|
||||
# How to Create a Cache Module
|
||||
|
||||
In this document, you will learn how to build your own Medusa cache module.
|
||||
|
||||
## Overview
|
||||
|
||||
Medusa provides ready-made modules for cache, including in-memory and Redis modules. If you prefer another technology used for caching in your commerce application, you can build a module locally and use it in your Medusa backend. You can also publish to NPM and reuse it across multiple Medusa backend instances.
|
||||
|
||||
In this document, you will learn how to build your own Medusa cache module based on Memcached as an example. This gives you a real-life example of creating the Cache Module. You can follow the general steps with any other caching system or service.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
If you want to create the Memcached cache module as explained in this guide, you must have [Memcached](https://memcached.org/) installed and running.
|
||||
|
||||
---
|
||||
|
||||
## (Optional) Step 0: Prepare Module Directory
|
||||
|
||||
Before you start implementing your module, it's recommended to prepare the directory or project holding your custom implementation.
|
||||
|
||||
You can refer to the [Project Preparation step in the Create Module documentation](../modules/create.mdx#optional-step-0-project-preparation) to learn how to do that.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create the Service
|
||||
|
||||
Create the file `src/services/memcached-cache.ts` which will hold your cache service. Note that the name of the file is recommended to be in the format `<service_name>-cache`. So, if you’re not integrating `memcached`, you should replace the name with what’s relevant for your module.
|
||||
|
||||
Add the following content to the file:
|
||||
|
||||
```ts title="src/services/memcached-cache.ts"
|
||||
import { ICacheService } from "@medusajs/types"
|
||||
|
||||
class MemcachedCacheService implements ICacheService {
|
||||
async get<T>(key: string): Promise<T> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async set(
|
||||
key: string,
|
||||
data: unknown,
|
||||
ttl?: number
|
||||
): Promise<void> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async invalidate(key: string): Promise<void> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
}
|
||||
|
||||
export default MemcachedCacheService
|
||||
```
|
||||
|
||||
This creates the class `MemcachedCacheService` that implements the `ICacheService` interface imported from `@medusajs/types`. Feel free to rename the class to what’s relevant for your cache service.
|
||||
|
||||
In the class, you must implement three methods: `get`, `set`, and `invalidate`. You’ll learn what each of these methods do in the upcoming section.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Implement the Methods
|
||||
|
||||
### constructor
|
||||
|
||||
The `constructor` method of a service allows you to prepare any third-party client or service necessary to be used in other methods. It also allows you to get access to the module’s options which are typically defined in `medusa-config.js`, and to other services and resources in the Medusa backend using [dependency injection](../fundamentals/dependency-injection.md).
|
||||
|
||||
Here’s an example of how you can use the `constructor` to create a memcached instance and save the module’s options:
|
||||
|
||||
```ts title="src/services/memcached-cache.ts"
|
||||
import { ICacheService } from "@medusajs/types"
|
||||
import Memcached from "memcached"
|
||||
|
||||
const DEFAULT_CACHE_TIME = 30
|
||||
|
||||
export type MemcachedCacheModuleOptions = {
|
||||
/**
|
||||
* Time to keep data in the cache (in seconds)
|
||||
*/
|
||||
ttl?: number
|
||||
|
||||
/**
|
||||
* Allow passing the configuration for Memcached client
|
||||
*/
|
||||
location: Memcached.Location
|
||||
options?: Memcached.options
|
||||
}
|
||||
|
||||
class MemcachedCacheService implements ICacheService {
|
||||
protected readonly memcached: Memcached
|
||||
protected readonly TTL: number
|
||||
|
||||
constructor(
|
||||
{
|
||||
// inject services through dependency injection
|
||||
// for example you can access the logger
|
||||
logger,
|
||||
},
|
||||
options: MemcachedCacheModuleOptions
|
||||
) {
|
||||
this.memcached = new Memcached(
|
||||
options.location,
|
||||
options.options
|
||||
)
|
||||
this.TTL = options.ttl || DEFAULT_CACHE_TIME
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### get
|
||||
|
||||
The `get` method allows you to retrieve the value of a cached item based on its key. The method accepts a string as a first parameter, which is the key in the cache. It either returns the cached item or `null` if it doesn’t exist.
|
||||
|
||||
Here’s an example implementation of this method for a Memcached service:
|
||||
|
||||
```ts title="src/services/memcached-cache.ts"
|
||||
class MemcachedCacheService implements ICacheService {
|
||||
// ...
|
||||
async get<T>(cacheKey: string): Promise<T | null> {
|
||||
return new Promise((res, rej) => {
|
||||
this.memcached.get(cacheKey, (err, data) => {
|
||||
if (err) {
|
||||
res(null)
|
||||
} else {
|
||||
if (data) {
|
||||
res(JSON.parse(data))
|
||||
} else {
|
||||
res(null)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### set
|
||||
|
||||
The `set` method is used to set an item in the cache. It accepts three parameters:
|
||||
|
||||
1. The first parameter `key` is a string that represents the key of the data being added to the cache. This key can be used later to get or invalidate the cached item.
|
||||
2. The second parameter `data` is the data to be added to the cache. There’s no defined type for this data.
|
||||
3. The third parameter `ttl` is optional. It’s a number indicating how long (in seconds) the data should be kept in the cache.
|
||||
|
||||
Here’s an example of an implementation of this method for a Memcached service:
|
||||
|
||||
```ts title="src/services/memcached-cache.ts"
|
||||
class MemcachedCacheService implements ICacheService {
|
||||
// ...
|
||||
async set(
|
||||
key: string,
|
||||
data: Record<string, unknown>,
|
||||
ttl: number = this.TTL
|
||||
): Promise<void> {
|
||||
return new Promise((res, rej) =>
|
||||
this.memcached.set(
|
||||
key, JSON.stringify(data), ttl, (err) => {
|
||||
if (err) {
|
||||
rej(err)
|
||||
} else {
|
||||
res()
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### invalidate
|
||||
|
||||
The `invalidate` method removes an item from the cache using its key. By default, the item is removed from the cache when the time-to-live (ttl) expires. The `invalidate` method can be used to remove the item beforehand.
|
||||
|
||||
The method accepts a string as a first parameter, which is the key of the item to invalidate and remove from the cache.
|
||||
|
||||
Here’s an example of an implementation of this method for a Memcached service:
|
||||
|
||||
```ts title="src/services/memcached-cache.ts"
|
||||
class MemcachedCacheService implements ICacheService {
|
||||
// ...
|
||||
async invalidate(key: string): Promise<void> {
|
||||
return new Promise((res, rej) => {
|
||||
this.memcached.del(key, (err) => {
|
||||
if (err) {
|
||||
rej(err)
|
||||
} else {
|
||||
res()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Export the Service
|
||||
|
||||
After implementing the cache service, you must export it so that the Medusa backend can use it.
|
||||
|
||||
Create the file `src/index.ts` with the following content:
|
||||
|
||||
```ts title="src/index.ts"
|
||||
import { ModuleExports } from "@medusajs/modules-sdk"
|
||||
|
||||
import {
|
||||
MemcachedCacheService,
|
||||
} from "./services/memcached-cache"
|
||||
|
||||
const service = MemcachedCacheService
|
||||
|
||||
const moduleDefinition: ModuleExports = {
|
||||
service,
|
||||
}
|
||||
|
||||
export default moduleDefinition
|
||||
```
|
||||
|
||||
This exports a module definition, which requires at least a `service`. If you named your service something other than `MemcachedCacheService`, make sure to replace it with that.
|
||||
|
||||
You can learn more about what other properties you can export in your module definition in the [Create a Module documentation](../modules/create.mdx#step-2-export-module).
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Test your Module
|
||||
|
||||
You can test your module in the Medusa backend by referencing it in the configurations.
|
||||
|
||||
To do that, add the module to the exported configuration in `medusa-config.js` as follows:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
// ...
|
||||
modules: {
|
||||
// ...
|
||||
cacheService: {
|
||||
resolve: "path/to/custom-module/src/index.ts",
|
||||
options: {
|
||||
// any necessary options
|
||||
ttl: 30,
|
||||
location: "localhost:55000",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Make sure to replace the `path/to/custom-module` with a relative path from your Medusa backend to your module. You can learn more about module reference in the [Create Module documentation](../modules/create.mdx#module-reference).
|
||||
|
||||
You can also add any necessary options to the module. The options added in the example above are relevant to the Memcached Module and you can replace them with your own options.
|
||||
|
||||
Then, to test the module, run the Medusa backend which also runs your module:
|
||||
|
||||
```bash npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## (Optional) Step 5: Publish your Module
|
||||
|
||||
You can publish your cache module to NPM. This can be useful if you want to reuse your module across Medusa backend instances, or want to allow other developers to use it.
|
||||
|
||||
You can refer to the [Publish Module documentation](../modules/publish.md) to learn how to publish your module.
|
||||
@@ -1,70 +0,0 @@
|
||||
---
|
||||
description: 'In this document, you’ll learn about the in-memory cache module and how you can install it in your Medusa backend.'
|
||||
---
|
||||
|
||||
# In-Memory Cache Module
|
||||
|
||||
In this document, you’ll learn about the in-memory cache module and how you can install it in your Medusa backend.
|
||||
|
||||
## Overview
|
||||
|
||||
Medusa’s modular architecture allows developers to extend or completely replace the logic used for caching. You can create a custom module, or you can use the modules Medusa provides.
|
||||
|
||||
Medusa’s default starter project uses the in-memory cache module. The in-memory cache module uses a plain JavaScript Map object to store the cache data.
|
||||
|
||||
This module is helpful for development or when you’re testing out Medusa, but it’s not recommended to be used in production. For production, it’s recommended to use modules like [Redis Cache Module](./redis.md).
|
||||
|
||||
This document will guide you through installing the in-memory cache module.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Backend
|
||||
|
||||
It’s assumed you already have a Medusa backend installed. If not, you can learn how to install it by following [this guide](../../backend/install.mdx).
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Install the Module
|
||||
|
||||
In the root directory of your Medusa backend, install the in-memory cache module with the following command:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install @medusajs/cache-inmemory
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Add Configuration
|
||||
|
||||
In `medusa-config.js`, add the following to the exported object:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
// ...
|
||||
modules: {
|
||||
// ...
|
||||
cacheService: {
|
||||
resolve: "@medusajs/cache-inmemory",
|
||||
options: {
|
||||
ttl: 30,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
This registers the in-memory cache module as the main cache service to use. You pass it the option `ttl`. This means time-to-live, and it indicates the number of seconds an item can live in the cache before it’s removed.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Test Module
|
||||
|
||||
To test the module, run the following command to start the Medusa backend:
|
||||
|
||||
```bash npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
The backend should then start with no errors, indicating that the module was installed successfully.
|
||||
@@ -1,94 +0,0 @@
|
||||
---
|
||||
description: 'In this document, you’ll learn about the Redis cache module and how you can install it in your Medusa backend.'
|
||||
---
|
||||
|
||||
# Redis Cache Module
|
||||
|
||||
In this document, you’ll learn about the Redis cache module and how you can install it in your Medusa backend.
|
||||
|
||||
## Overview
|
||||
|
||||
Medusa’s modular architecture allows developers to extend or replace the logic used for [caching](../overview.mdx). You can create a custom module, or you can use the modules Medusa provides.
|
||||
|
||||
One of these modules is the Redis Module. This module allows you to utilize Redis for the caching functionality. This document will you guide you through installing the Redis Module.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Backend
|
||||
|
||||
It’s assumed you already have a Medusa backend installed. If not, you can learn how to install it by following [this guide](../../backend/install.mdx).
|
||||
|
||||
### Redis
|
||||
|
||||
You must have Redis installed and configured in your Medusa backend. You can learn how to install it from the [Redis documentation](https://redis.io/docs/getting-started/installation/).
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Install the Module
|
||||
|
||||
In the root directory of your Medusa backend, install the Redis cache module with the following command:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install @medusajs/cache-redis
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Add Environment Variable
|
||||
|
||||
The Redis cache module requires a connection URL to Redis as part of its options. If you don’t already have an environment variable set for a Redis URL, make sure to add one:
|
||||
|
||||
```bash
|
||||
CACHE_REDIS_URL=<YOUR_REDIS_URL>
|
||||
```
|
||||
|
||||
Where `<YOUR_REDIS_URL>` is a connection URL to your Redis instance.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Add Configuration
|
||||
|
||||
In `medusa-config.js`, add the following to the exported object:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
// ...
|
||||
modules: {
|
||||
// ...
|
||||
cacheService: {
|
||||
resolve: "@medusajs/cache-redis",
|
||||
options: {
|
||||
redisUrl: process.env.CACHE_REDIS_URL,
|
||||
ttl: 30,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
This registers the Redis cache module as the main cache service to use. In the options, you pass `redisUrl` with the value being the environment variable you set.
|
||||
|
||||
You also pass the option `ttl`. This means time-to-live, and it indicates the number of seconds an item can live in the cache before it’s removed. If it's set to `0`, the module will skip adding the items to the cache.
|
||||
|
||||
Other available options include:
|
||||
|
||||
- `redisOptions`: an object containing options for the Redis instance. You can learn about available options in [io-redis’s documentation](https://redis.github.io/ioredis/classes/Redis.html#options). By default, it’s an empty object.
|
||||
- `namespace`: a string used to prefix event keys. By default, it's `medusa`.
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Test Module
|
||||
|
||||
To test the module, run the following command to start the Medusa backend:
|
||||
|
||||
```bash npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
If the module was installed successfully, you should see the following message in the logs:
|
||||
|
||||
```bash noCopy noReport
|
||||
Connection to Redis in module 'cache-redis' established
|
||||
```
|
||||
@@ -1,65 +0,0 @@
|
||||
---
|
||||
description: 'In this document, you’ll learn about Cache Modules in Medusa, how they work, and which modules Medusa provides.'
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import DocCard from '@theme/DocCard';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Cache
|
||||
|
||||
In this document, you’ll learn about Cache Modules in Medusa, how they work, and which modules Medusa provides.
|
||||
|
||||
## Overview
|
||||
|
||||
Cache is used in Medusa to store the results of computations such as price selection or various tax calculations. The caching layer in Medusa is implemented as a [module](../modules/overview.mdx). This allows you to replace the underlying database/cache store in your project.
|
||||
|
||||
Modules are packages that export a service. The main service exported from a cache module has to implement the `ICacheService` interface exported from the `@medusajs/types` package.
|
||||
|
||||
During the app startup, the modules loader will load the service into the [dependency injection container](../fundamentals/dependency-injection.md) to be available as `cacheService` to the rest of the commerce application.
|
||||
|
||||
---
|
||||
|
||||
## Available Modules
|
||||
|
||||
Medusa default starter project comes with the in-memory cache module (`@medusajs/cache-inmemory`). In-memory cache uses a plain JS Map object to store data, which is great for testing and development purposes.
|
||||
|
||||
For production environments, there is the Redis cache module package (`@medusajs/cache-redis`) that you can install.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/cache/modules/redis',
|
||||
label: 'Redis Module',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to install and use the Redis Module'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/cache/modules/in-memory',
|
||||
label: 'In-Memory Module',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to install and use the In-Memory Module'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Custom Development
|
||||
|
||||
Developers can create custom cache modules to implement their custom cache logic. The module can be installed and used in any Medusa backend.
|
||||
|
||||
<DocCard item={{
|
||||
type: 'link',
|
||||
href: '/development/cache/create',
|
||||
label: 'Create a Cache Module',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a cache module in Medusa.'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@@ -1,164 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to create an entity in Medusa. This guide also explains how to create a repository and access and delete the entity.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Create an Entity
|
||||
|
||||
In this document, you’ll learn how you can create a custom [Entity](./overview.mdx).
|
||||
|
||||
## Step 1: Create the Entity
|
||||
|
||||
To create an entity, create a TypeScript file in `src/models`. For example, here’s a `Post` entity defined in the file `src/models/post.ts`:
|
||||
|
||||
:::note
|
||||
|
||||
Entities can only be placed in the top level of the `src/models` directory. So, you can't create an entity in a subfolder.
|
||||
|
||||
:::
|
||||
|
||||
```ts title="src/models/post.ts"
|
||||
import {
|
||||
BeforeInsert,
|
||||
Column,
|
||||
Entity,
|
||||
PrimaryColumn,
|
||||
} from "typeorm"
|
||||
import { BaseEntity } from "@medusajs/medusa"
|
||||
import { generateEntityId } from "@medusajs/medusa/dist/utils"
|
||||
|
||||
@Entity()
|
||||
export class Post extends BaseEntity {
|
||||
@Column({ type: "varchar" })
|
||||
title: string | null
|
||||
|
||||
@BeforeInsert()
|
||||
private beforeInsert(): void {
|
||||
this.id = generateEntityId(this.id, "post")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This entity has one column `title` defined. However, since it extends `BaseEntity` it will also have the `id`, `created_at`, and `updated_at` columns.
|
||||
|
||||
Medusa’s core entities all have the following format for IDs: `<PREFIX>_<RANDOM>`. For example, an order might have the ID `order_01G35WVGY4D1JCA4TPGVXPGCQM`.
|
||||
|
||||
To generate an ID for your entity that matches the IDs generated for Medusa’s core entities, you should add a `BeforeInsert` event handler. Then, inside that handler use Medusa’s utility function `generateEntityId` to generate the ID. It accepts the ID as a first parameter and the prefix as a second parameter. The `Post` entity IDs will be of the format `post_<RANDOM>`.
|
||||
|
||||
You can learn more about what decorators and column types you can use in [Typeorm’s documentation](https://typeorm.io/entities).
|
||||
|
||||
### Soft-Deletable Entities
|
||||
|
||||
If you want the entity to also be soft deletable then it should extend `SoftDeletableEntity` instead:
|
||||
|
||||
```ts
|
||||
import { SoftDeletableEntity } from "@medusajs/medusa"
|
||||
|
||||
@Entity()
|
||||
export class Post extends SoftDeletableEntity {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Adding Relations
|
||||
|
||||
Your entity may be related to another entity. You can showcase the relation with [Typeorm's relation decorators](https://typeorm.io/relations).
|
||||
|
||||
For example, you can create another entity `Author` and add a `ManyToOne` relation to it from the `Post`, and a `OneToMany` relation from the `Author` to the `Post`:
|
||||
|
||||
<Tabs groupId="files" isCodeTabs={true}>
|
||||
<TabItem value="post" label="src/models/post.ts" default>
|
||||
|
||||
```ts
|
||||
import {
|
||||
BeforeInsert,
|
||||
Column,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
} from "typeorm"
|
||||
import { BaseEntity } from "@medusajs/medusa"
|
||||
import { generateEntityId } from "@medusajs/medusa/dist/utils"
|
||||
import { Author } from "./author"
|
||||
|
||||
@Entity()
|
||||
export class Post extends BaseEntity {
|
||||
@Column({ type: "varchar" })
|
||||
title: string | null
|
||||
|
||||
@Column({ type: "varchar" })
|
||||
author_id: string
|
||||
|
||||
@ManyToOne(() => Author, (author) => author.posts)
|
||||
@JoinColumn({ name: "author_id" })
|
||||
author: Author
|
||||
|
||||
@BeforeInsert()
|
||||
private beforeInsert(): void {
|
||||
this.id = generateEntityId(this.id, "post")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="author" label="src/models/author.ts">
|
||||
|
||||
```ts
|
||||
import { BaseEntity, generateEntityId } from "@medusajs/medusa"
|
||||
import {
|
||||
BeforeInsert,
|
||||
Column,
|
||||
Entity,
|
||||
OneToMany,
|
||||
} from "typeorm"
|
||||
import { Post } from "./post"
|
||||
|
||||
@Entity()
|
||||
export class Author extends BaseEntity {
|
||||
@Column({ type: "varchar" })
|
||||
name: string
|
||||
|
||||
@Column({ type: "varchar", nullable: true })
|
||||
image?: string
|
||||
|
||||
@OneToMany(() => Post, (post) => post.author)
|
||||
posts: Post[]
|
||||
|
||||
@BeforeInsert()
|
||||
private beforeInsert(): void {
|
||||
this.id = generateEntityId(this.id, "auth")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Adding these relations allows you to later on expand these relations when retrieving records of this entity with repositories.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Create and Run Migrations
|
||||
|
||||
Additionally, you must create a migration for your entity. Migrations are used to update the database schema with new tables or changes to existing tables.
|
||||
|
||||
You can learn more about Migrations, how to create or generate them, and how to run them in the [Migration documentation](./migrations/create.md).
|
||||
|
||||
Make sure to run the migrations before you start using the entity.
|
||||
|
||||
---
|
||||
|
||||
## Advanced Entity Definitions
|
||||
|
||||
With entities, you can create relationships, index keys, and more. As Medusa uses Typeorm, you can learn about using these functionalities through [Typeorm's documentation](https://typeorm.io/entities).
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to use repositories](./repositories.md)
|
||||
- [Extend an entity](./extend-entity.md)
|
||||
- [Create a plugin](../plugins/create.mdx)
|
||||
@@ -1,183 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to extend a core entity in Medusa to add custom attributes.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Extend an Entity
|
||||
|
||||
In this document, you’ll learn how to extend a core entity in Medusa.
|
||||
|
||||
## Overview
|
||||
|
||||
Medusa uses entities to represent tables in the database. As you build your custom commerce application, you’ll often need to add your own properties to those entities. This guide explains the necessary steps to extend core Medusa entities.
|
||||
|
||||
This guide will use the Product entity as an example to demonstrate the steps.
|
||||
|
||||
### Word of Caution about Overriding
|
||||
|
||||
Extending entities to add new attributes or methods shouldn't cause any issues within your commerce application. However, if you extend them to override their existing methods or attributes, you should be aware that this could have negative implications, such as unanticipated bugs, especially when you try to upgrade the core Medusa package to a newer version.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create Entity File
|
||||
|
||||
In your Medusa backend, create the file `src/models/product.ts`. This file will hold your extended entity.
|
||||
|
||||
Note that the name of the file must be the same as the name of the original entity in the core package. Since in this guide you’re overriding the Product entity, it’s named `product` to match the core. If you’re extending the customer entity, for example, the file should be named `customer.ts`.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Implement Extended Entity
|
||||
|
||||
In the file you created, you can import the entity you’re extending from the core package, then create a class that extends that entity. You can add in that class the new attributes and methods.
|
||||
|
||||
Here’s an example of extending the Product entity:
|
||||
|
||||
```ts title="src/models/product.ts"
|
||||
import { Column, Entity } from "typeorm"
|
||||
import {
|
||||
// alias the core entity to not cause a naming conflict
|
||||
Product as MedusaProduct,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
@Entity()
|
||||
export class Product extends MedusaProduct {
|
||||
@Column()
|
||||
customAttribute: string
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## (Optional) Step 3: Create a TypeScript Declaration File
|
||||
|
||||
If you’re using JavaScript instead of TypeScript in your implementation, you can skip this step.
|
||||
|
||||
To ensure that TypeScript is aware of your extended entity and affects the typing of the Medusa package itself, create the file `src/index.d.ts` with the following content:
|
||||
|
||||
```ts title="src/index.d.ts"
|
||||
export declare module "@medusajs/medusa/dist/models/product" {
|
||||
declare interface Product {
|
||||
customAttribute: string;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notice that you must pass the attributes you added to the entity into the `interface`. The attributes will be merged with the attributes defined in the core `Product` entity.
|
||||
|
||||
:::note
|
||||
|
||||
If TypeScript still doesn't pick up the custom attributes after adding `src/index.d.ts`, try moving the file to a nested directory in `src`. Please note that this only ensures TypeScript picks up the new attributes, but it doesn't actually reflect the new attributes in the database. That is handled by migrations.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Create Migration
|
||||
|
||||
To reflect your entity changes on the database schema, you must create a migration with those changes.
|
||||
|
||||
You can learn how to create or generate a migration in [this documentation](./migrations/create.md).
|
||||
|
||||
Here’s an example of a migration of the entity extended in this guide:
|
||||
|
||||
```ts title="src/migrations/1680013376180-changeProduct.ts"
|
||||
import { MigrationInterface, QueryRunner } from "typeorm"
|
||||
|
||||
class changeProduct1680013376180 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
"ALTER TABLE \"product\"" +
|
||||
" ADD COLUMN \"customAttribute\" text"
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
"ALTER TABLE \"product\" DROP COLUMN \"customAttribute\""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default changeProduct1680013376180
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Use Custom Entity
|
||||
|
||||
For changes to take effect, you must transpile your code by running the `build` command in the root of the Medusa backend:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
Then, run the following command to migrate your changes to the database:
|
||||
|
||||
```bash
|
||||
npx medusa migrations run
|
||||
```
|
||||
|
||||
You should see that your migration was executed, which means your changes were reflected in the database schema.
|
||||
|
||||
You can now use your extended entity throughout your commerce application.
|
||||
|
||||
---
|
||||
|
||||
## Access Custom Attributes and Relations in Core API Routes
|
||||
|
||||
### Request Parameters
|
||||
|
||||
In most cases, after you extend an entity to add new attributes, you'll likely need to pass these attributes to API Routes defined in the core. By default, this causes an error, as request parameters are validated to ensure only those that are defined are passed to the API Route.
|
||||
|
||||
To allow passing your custom attribute, you'll need to [extend the validator](../api-routes/extend-validator.md) of the API Route.
|
||||
|
||||
### Response Fields
|
||||
|
||||
After you add custom attributes, you'll notice that these attributes aren't returned as part of the response fields of core API Routes. Core API Routes have a defined set of fields and relations that can be returned by default in requests.
|
||||
|
||||
To change that and ensure your custom attribute is returned in your request, you can extend the allowed fields of a set of API Routes in a loader file and add your attribute into them.
|
||||
|
||||
For example, if you added a custom attribute in the `Product` entity and you want to ensure it's returned in all the product's store API Routes (API Routes under the prefix `/store/products`), you can create a file under the `src/loaders` directory in your Medusa backend with the following content:
|
||||
|
||||
```ts title="src/loaders/extend-product-fields.ts"
|
||||
export default async function () {
|
||||
const imports = (await import(
|
||||
"@medusajs/medusa/dist/api/routes/store/products/index"
|
||||
)) as any
|
||||
imports.allowedStoreProductsFields = [
|
||||
...imports.allowedStoreProductsFields,
|
||||
"customAttribute",
|
||||
]
|
||||
imports.defaultStoreProductsFields = [
|
||||
...imports.defaultStoreProductsFields,
|
||||
"customAttribute",
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
In the code snippet above, you import `@medusajs/medusa/dist/api/routes/store/products/index`, which is where all the product's store API Routes are exported. In that file, there are the following defined variables:
|
||||
|
||||
- `allowedStoreProductsFields`: The fields or attributes of a product that are allowed to be retrieved and returned in the product's store API Routes. This would allow you to pass your custom attribute in the `fields` request parameter of the product's store API Routes.
|
||||
- `defaultStoreProductsFields`: The fields or attributes of a product that are retrieved and returned by default in the product's store API Routes.
|
||||
|
||||
You change the values of these variables and pass the name of your custom attribute. Make sure to change `customAttribute` to the name of your custom attribute.
|
||||
|
||||
:::tip
|
||||
|
||||
Before you test out the above change, make sure to build your changes before you start the Medusa backend.
|
||||
|
||||
:::
|
||||
|
||||
You can also add custom relations by changing the following defined variables:
|
||||
|
||||
- `allowedStoreProductsRelations`: The relations of a product that are allowed to be retrieved and returned in the product's store API Routes. This would allow you to pass your custom relation in the `expand` request parameter of the product's store API Routes.
|
||||
- `defaultStoreProductsRelations`: The relations of a product that are retrieved and returned by default in the product's store API Routes.
|
||||
|
||||
If you want to apply this example for a different entity or set of API Routes, you would need to change the import path `@medusajs/medusa/dist/api/routes/store/products/index` to the path of the API Routes you're targeting. You also need to change `allowedStoreProductsFields` and `defaultStoreProductsFields` to the names of the variables in that file, and the same goes for relations. Typically, these names would be of the format `(allowed|default)(Store|Admin)(Entity)(Fields|Relation)`.
|
||||
|
||||
---
|
||||
|
||||
## Advanced Entity Definitions
|
||||
|
||||
With entities, you can create relationships, index keys, and more. As Medusa uses Typeorm, you can learn about using these functionalities through [Typeorm's documentation](https://typeorm.io/).
|
||||
@@ -1,136 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to extend a core repository in Medusa to add custom methods.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Extend a Repository
|
||||
|
||||
In this document, you’ll learn how to extend a core repository in Medusa.
|
||||
|
||||
## Overview
|
||||
|
||||
Medusa uses Typeorm’s Repositories to perform operations on an entity, such as retrieve or update the entity. Typeorm already provides these basic functionalities within a repository, but sometimes you need to implement a custom implementation to handle the logic behind these operations differently. You might also want to add custom methods related to processing entities that aren’t available in the default repositories.
|
||||
|
||||
In this guide, you’ll learn how to extend a repository in the core Medusa package. This guide will use the Product repository as an example to demonstrate the steps.
|
||||
|
||||
### Word of Caution about Overriding
|
||||
|
||||
Extending repositories to add new methods shouldn't cause any issues within your commerce application. However, if you extend them to override their existing methods, you should be aware that this could have negative implications, such as unanticipated bugs, especially when you try to upgrade the core Medusa package to a newer version.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create Repository File
|
||||
|
||||
In your Medusa backend, create the file `src/repositories/product.ts`. This file will hold your extended repository.
|
||||
|
||||
Note that the name of the file must be the same as the name of the original repository in the core package. Since in this guide you’re extending the Product repository, it’s named `product` to match the core. If you’re extending the customer repository, for example, the file should be named `customer.ts`.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Implement Extended Repository
|
||||
|
||||
In the file you created, you must retrieve both the repository you're extending along with its entity from the core. You’ll then use the data source exported from the core package to extend the repository.
|
||||
|
||||
:::tip
|
||||
|
||||
A data source is Typeorm’s connection settings that allows you to connect to your database. You can learn more about it in [Typeorm’s documentation](https://typeorm.io/data-source).
|
||||
|
||||
:::
|
||||
|
||||
Here’s an example of the implementation of the extended Product repository:
|
||||
|
||||
```ts title="src/repositories/product.ts"
|
||||
import { Product } from "@medusajs/medusa"
|
||||
import {
|
||||
dataSource,
|
||||
} from "@medusajs/medusa/dist/loaders/database"
|
||||
import {
|
||||
// alias the core repository to not cause a naming conflict
|
||||
ProductRepository as MedusaProductRepository,
|
||||
} from "@medusajs/medusa/dist/repositories/product"
|
||||
|
||||
export const ProductRepository = dataSource
|
||||
.getRepository(Product)
|
||||
.extend({
|
||||
// it is important to spread the existing repository here.
|
||||
// Otherwise you will end up losing core properties
|
||||
...Object.assign(MedusaProductRepository, {
|
||||
target: Product,
|
||||
}),
|
||||
|
||||
/**
|
||||
* Here you can create your custom function
|
||||
* For example
|
||||
*/
|
||||
customFunction(): void {
|
||||
// TODO add custom implementation
|
||||
return
|
||||
},
|
||||
})
|
||||
|
||||
export default ProductRepository
|
||||
```
|
||||
|
||||
You first import all necessary resources from the core package: the `Product` entity, the `dataSource` instance, and the core’s `ProductRepository` aliased as `MedusaProductRepository` to avoid naming conflict.
|
||||
|
||||
You then use the `dataSource` instance to retrieve the `Product` entity’s repository and extend it using the repository’s `extend` method. This method is available as part of Typeorm Repository API. This method returns your extended repository.
|
||||
|
||||
The `extend` method accepts an object with all the methods to add to the extended repository.
|
||||
|
||||
You must first add the properties of the repository you’re extending, which in this case is the product repository (aliased as `MedusaProductRepository`). This will ensure you don’t lose core methods, which can lead to the core not working as expected. You add use the spread operator (`…`) with the `MedusaProductRepository` to spread its properties.
|
||||
|
||||
After that, you can add your custom methods to the repository. In the example above, you add the method `customFunction`. You can use any name for your methods.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Use Your Extended Repository
|
||||
|
||||
You can now use your extended repository in other resources such as services or API Routes.
|
||||
|
||||
Here’s an example of using it in an API Route:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
import ProductRepository from "./path/to/product.ts"
|
||||
import { EntityManager } from "typeorm"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
// ...
|
||||
|
||||
const productRepository: typeof ProductRepository =
|
||||
req.scope.resolve(
|
||||
"productRepository"
|
||||
)
|
||||
const manager: EntityManager = req.scope.resolve("manager")
|
||||
const productRepo = manager.withRepository(
|
||||
productRepository
|
||||
)
|
||||
productRepo.customFunction()
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Test Your Implementation
|
||||
|
||||
For changes to take effect, you must transpile your code by running the `build` command in the root of the Medusa backend:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
Then, run the following command to start your backend:
|
||||
|
||||
```bash npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
You should see your custom implementation working as expected.
|
||||
@@ -1,152 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to create a migration in Medusa. This guide explains how to write and run migrations.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Create Migrations
|
||||
|
||||
In this document, you’ll learn how to create a [Migration](./overview.mdx) using [Typeorm](https://typeorm.io) in Medusa.
|
||||
|
||||
## Step 1: Create Migration File
|
||||
|
||||
There are two ways to create a migration file: create and write its content manually, or create and generate its content.
|
||||
|
||||
If you're creating a custom entity, then it's recommended to generate the migration file. However, if you're extending an entity from Medusa's core, then you should create and write the migration manually.
|
||||
|
||||
### Option 1: Generate Migration File
|
||||
|
||||
:::warning
|
||||
|
||||
Generating migration files for extended entities may cause unexpected errors. It's highly recommended to write them manually instead.
|
||||
|
||||
:::
|
||||
|
||||
Typeorm provides a `migration:generate` command that allows you to pass it a Typeorm [DataSource](https://typeorm.io/data-source). The `DataSource` includes database connection details, as well as the path to your custom entities.
|
||||
|
||||
Start by creating the file `datasource.js` in the root of your Medusa backend project with the following content:
|
||||
|
||||
```js
|
||||
const { DataSource } = require("typeorm")
|
||||
|
||||
const AppDataSource = new DataSource({
|
||||
type: "postgres",
|
||||
port: 5432,
|
||||
username: "<YOUR_DB_USERNAME>",
|
||||
password: "<YOUR_DB_PASSWORD>",
|
||||
database: "<YOUR_DB_NAME>",
|
||||
entities: [
|
||||
"dist/models/*.js",
|
||||
],
|
||||
migrations: [
|
||||
"dist/migrations/*.js",
|
||||
],
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
datasource: AppDataSource,
|
||||
}
|
||||
```
|
||||
|
||||
Make sure to replace `<YOUR_DB_USERNAME>`, `<YOUR_DB_PASSWORD>`, and `<YOUR_DB_NAME>` with the necessary values for your database connection.
|
||||
|
||||
Then, after creating your entity, run the `build` command:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
Finally, run the following command to generate a migration for your custom entity:
|
||||
|
||||
```bash
|
||||
npx typeorm migration:generate -d datasource.js src/migrations/PostCreate
|
||||
```
|
||||
|
||||
This will generate the migration file in the path you specify, where `PostCreate` is just an example of the name of the migration to create. The migration file must be inside the `src/migrations` directory. When you run the build command, it will be transpiled into the `dist/migrations` directory.
|
||||
|
||||
The `migrations run` command can only pick up migrations under the `dist/migrations` directory on a Medusa backend. This applies to migrations created in a Medusa backend, and not in a Medusa plugin. For plugins, check out the [Plugin's Structure section](../../plugins/create.mdx).
|
||||
|
||||
You can now continue to [step 2](#step-2-build-files) of this guide.
|
||||
|
||||
### Option 2: Write Migration File
|
||||
|
||||
With this option, you'll use Typeorm's CLI tool to create the migration file, but you'll write the content yourself.
|
||||
|
||||
Run the following command in the root directory of your Medusa backend project:
|
||||
|
||||
```bash
|
||||
npx typeorm migration:create src/migrations/PostCreate
|
||||
```
|
||||
|
||||
This will create the migration file in the path you specify, where `PostCreate` is just an example of the name of the migration to create. The migration file must be inside the `src/migrations` directory. When you run the build command, it will be transpiled into the `dist/migrations` directory.
|
||||
|
||||
The `migrations run` command can only pick up migrations under the `dist/migrations` directory on a Medusa backend. This applies to migrations created in a Medusa backend, and not in a Medusa plugin. For plugins, check out the [Plugin's Structure section](../../plugins/create.mdx).
|
||||
|
||||
If you open the file, you'll find `up` and `down` methods. The `up` method is used to reflect the changes on the database. The `down` method is used to revert the changes, which will be executed if the `npx medusa migrations revert` command is used.
|
||||
|
||||
In each of the `up` and `down` methods, you can write the migration either with [SQL syntax](https://www.postgresql.org/docs/current/sql-syntax.html), or using the [migration API](https://typeorm.io/migrations#using-migration-api-to-write-migrations).
|
||||
|
||||
For example:
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
import { MigrationInterface, QueryRunner } from "typeorm"
|
||||
|
||||
export class AddAuthorsAndPosts1690876698954 implements MigrationInterface {
|
||||
name = "AddAuthorsAndPosts1690876698954"
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "post" ("id" character varying NOT NULL, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "title" character varying NOT NULL, "author_id" character varying NOT NULL, "authorId" character varying, CONSTRAINT "PK_be5fda3aac270b134ff9c21cdee" PRIMARY KEY ("id"))`)
|
||||
await queryRunner.query(`CREATE TABLE "author" ("id" character varying NOT NULL, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "name" character varying NOT NULL, "image" character varying, CONSTRAINT "PK_5a0e79799d372fe56f2f3fa6871" PRIMARY KEY ("id"))`)
|
||||
await queryRunner.query(`ALTER TABLE "post" ADD CONSTRAINT "FK_c6fb082a3114f35d0cc27c518e0" FOREIGN KEY ("authorId") REFERENCES "author"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "post" DROP CONSTRAINT "FK_c6fb082a3114f35d0cc27c518e0"`)
|
||||
await queryRunner.query(`DROP TABLE "author"`)
|
||||
await queryRunner.query(`DROP TABLE "post"`)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Migration Name
|
||||
|
||||
If the `name` attribute isn't available in the generated migration, an error may occur while running the migration. To avoid this, make sure to add it manually and set its value to the class's name:
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
export class AddAuthorsAndPosts1690876698954 implements MigrationInterface {
|
||||
name = "AddAuthorsAndPosts1690876698954"
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Build Files
|
||||
|
||||
Before you can run the migrations, you need to run the build command to transpile the TypeScript files to JavaScript files:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Run Migration
|
||||
|
||||
The last step is to run the migration with the command detailed earlier
|
||||
|
||||
```bash
|
||||
npx medusa migrations run
|
||||
```
|
||||
|
||||
If you check your database now you should see that the change defined by the migration has been applied successfully.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Create a Plugin](../../plugins/create.mdx)
|
||||
@@ -1,89 +0,0 @@
|
||||
---
|
||||
description: 'Learn what Migrations are in Medusa and how to run them. Migrations are used to make changes to the database schema that Medusa is linked to.'
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Migrations
|
||||
|
||||
In this document, you'll learn what Migrations are in Medusa.
|
||||
|
||||
## What are Migrations
|
||||
|
||||
Migrations are scripts that are used to make additions or changes to your database schema. In Medusa, they are essential for both when you first install your backend and for subsequent backend upgrades later on.
|
||||
|
||||
When you first create your Medusa backend, the database schema used must have all the tables necessary for the backend to run.
|
||||
|
||||
When a new Medusa version introduces changes to the database schema, you'll have to run migrations to apply them to your own database.
|
||||
|
||||
:::tip
|
||||
|
||||
Migrations are used to apply changes to the database schema. However, there are some version updates of Medusa that require updating the data in your database to fit the new schema. Those are specific to each version and you should check out the version under Upgrade Guides for details on the steps.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Migration Commands
|
||||
|
||||
### Migrate Command
|
||||
|
||||
Using the Medusa CLI tool, you can run migrations with the following command:
|
||||
|
||||
```bash
|
||||
npx medusa migrations run
|
||||
```
|
||||
|
||||
This will check for any migrations that contain changes to your database schema that aren't applied yet and run them on your backend.
|
||||
|
||||
### Seed Command
|
||||
|
||||
Seeding is the process of filling your database with data that is either essential or for testing and demo purposes. In Medusa, the `seed` command will run the migrations to your database if necessary before it seeds your database with dummy data.
|
||||
|
||||
You can use the following command to seed your database:
|
||||
|
||||
```bash
|
||||
npx @medusajs/medusa-cli@latest seed -f ./data/seed.json
|
||||
```
|
||||
|
||||
This assumes that you have the file `data/seed.json` in your Medusa backend, which is available by default. It includes the demo data to seed into your database.
|
||||
|
||||
### Revert Migrations
|
||||
|
||||
To revert the last migration you ran, run the following command:
|
||||
|
||||
```bash
|
||||
npx @medusajs/medusa-cli@latest migrations revert
|
||||
```
|
||||
|
||||
### Other Migrations Commands
|
||||
|
||||
You can learn about migration commands available in the Medusa CLI tool by referring to the [Medusa CLI reference](../../../cli/reference.mdx#migrations)
|
||||
|
||||
---
|
||||
|
||||
## Custom Development
|
||||
|
||||
Developers can create custom entities in the Medusa backend, a plugin, or in a module, then ensure it reflects in the database using a migration.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/entities/migrations/create',
|
||||
label: 'Create a Migration',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create migrations in Medusa.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/entities/create',
|
||||
label: 'Create an API Route',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create API Routes in Medusa.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
@@ -1,127 +0,0 @@
|
||||
---
|
||||
description: 'Learn what entities are in Medusa. There are entities defined in the Medusa backend, and developers can create custom entities.'
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import Icons from '@theme/Icon';
|
||||
import LearningPath from '@site/src/components/LearningPath';
|
||||
|
||||
# Entities
|
||||
|
||||
In this document, you'll learn what Entities are in Medusa.
|
||||
|
||||
## What are Entities
|
||||
|
||||
Entities in medusa represent tables in the database as classes. An example of this would be the `Order` entity which represents the `order` table in the database. Entities provide a uniform way of defining and interacting with data retrieved from the database.
|
||||
|
||||
Aside from the entities in the Medusa core package, you can also create custom entities to use in your Medusa backend. Custom entities are TypeScript or JavaScript files located in the `src/models` directory of your Medusa backend. You then transpile these entities to be used during the backend's runtime using the `build` command, which moves them to the `dist/models` directory.
|
||||
|
||||
Entities are based on [Typeorm’s Entities](https://typeorm.io/entities) and use Typeorm decorators. Each entity also require a [repository](./repositories.md) to be created. A repository provides basic methods to access and manipulate the entity's data.
|
||||
|
||||
<LearningPath pathName="entity-and-api" />
|
||||
|
||||
---
|
||||
|
||||
## Base Entities
|
||||
|
||||
All entities must extend either the `BaseEntity` or `SoftDeletableEntity` classes. The `BaseEntity` class holds common columns including the `id`, `created_at`, and `updated_at` columns.
|
||||
|
||||
The `SoftDeletableEntity` class extends the `BaseEntity` class and adds another column `deleted_at`. If an entity can be soft deleted, meaning that a row in it can appear to the user as deleted but still be available in the database, it should extend `SoftDeletableEntity`.
|
||||
|
||||
---
|
||||
|
||||
## metadata Attribute
|
||||
|
||||
Most entities in Medusa have a `metadata` attribute. This attribute is an object that can be used to store custom data related to that entity. In the database, this attribute is stored as a [JSON Binary (JSONB)](https://www.postgresql.org/docs/current/datatype-json.html#JSON-CONTAINMENT) column. On retrieval, the attribute is parsed into an object.
|
||||
|
||||
Some example use cases for the `metadata` attribute include:
|
||||
|
||||
- Store an external ID of an entity related to a third-party integration.
|
||||
- Store product customization such as personalization options.
|
||||
|
||||
### Add and Update Metadata
|
||||
|
||||
You can add or update metadata entities either through the REST APIs or through create and update methods in the entity's respective [service](../services/overview.mdx).
|
||||
|
||||
In the [admin REST APIs](https://docs.medusajs.com/api/admin), you'll find that in create or update requests of some entities you can also set the `metadata`.
|
||||
|
||||
In services, there are typically `create` or `update` methods that allow you to set or update the metadata.
|
||||
|
||||
If you want to add a property to the `metadata` object or update a property in the `metadata` object, you can pass the `metadata` object with the properties you want to add or update in it. For example:
|
||||
|
||||
```json
|
||||
{
|
||||
// other data
|
||||
"metadata": {
|
||||
"is_b2b": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you want to remove a property from the `metadata` object, you can pass the `metadata` object with the property you want to delete. The property should have an empty string value. For example:
|
||||
|
||||
```json
|
||||
{
|
||||
// other data
|
||||
"metadata": {
|
||||
"is_b2b": "" // this deletes the `is_b2b` property from `metadata`
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Custom Development
|
||||
|
||||
Developers can create custom entities in the Medusa backend, a plugin, or in a module, then ensure it reflects in the database using a migration.
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/entities/create',
|
||||
label: 'Create an Entity',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create an entity in Medusa.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/entities/repositories',
|
||||
label: 'Create a Repository',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to use a repository in Medusa.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/entities/migrations/create',
|
||||
label: 'Create a Migration',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create migrations in Medusa.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/entities/extend-entity',
|
||||
label: 'Extend an Entity',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to extend a core Medusa entity.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/entities/extend-repository',
|
||||
label: 'Extend a Repository',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to extend a core Medusa repository.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
@@ -1,381 +0,0 @@
|
||||
---
|
||||
description: "In this document, you'll learn what repositories are, how to use them within your Medusa backend, and what are some of their common methods."
|
||||
---
|
||||
|
||||
# Repositories
|
||||
|
||||
In this document, you'll learn what repositories are, how to use them within your Medusa backend, and what are some of their common methods.
|
||||
|
||||
## Overview
|
||||
|
||||
Repositories provide generic helper methods for entities. For example, you can use the `find` method to retrieve all entities with pagination, or `findOne` to retrieve a single entity record.
|
||||
|
||||
Repositories are [Typeorm repositories](https://typeorm.io/working-with-repository), so you can refer to Typeorm's documentation on all available methods.
|
||||
|
||||
By default, you don't need to create a repository for your custom entities. You can retrieve the default repository of an entity using the Entity Manager. You should only create a repository if you want to implement custom methods in it.
|
||||
|
||||
---
|
||||
|
||||
## Access Default Repository
|
||||
|
||||
If you haven't created a custom repository, you can access the default repository using the [Entity Manager](https://typeorm.io/entity-manager-api). The Entity Manager is registered in the [dependency container](../fundamentals/dependency-injection.md) under the name `manager`. So, you can [resolve it](../fundamentals/dependency-injection.md#resolve-resources) and use its method `getRepository` to retrieve the repository of an entity.
|
||||
|
||||
For example, to retrieve the default repository of an entity in a service:
|
||||
|
||||
```ts title="src/services/post.ts"
|
||||
import { Post } from "../models/post"
|
||||
|
||||
class PostService extends TransactionBaseService {
|
||||
// ...
|
||||
|
||||
async list(): Promise<Post[]> {
|
||||
const postRepo = this.activeManager_.getRepository(
|
||||
Post
|
||||
)
|
||||
return await postRepo.find()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Another example is retrieving the default repository of an entity in an API Route:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
import { Post } from "../models/post"
|
||||
import { EntityManager } from "typeorm"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const manager: EntityManager = req.scope.resolve("manager")
|
||||
const postRepo = manager.getRepository(Post)
|
||||
|
||||
return res.json({
|
||||
posts: await postRepo.find(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Create Custom Repository
|
||||
|
||||
If you want to add custom methods or override existing methods in a repository, you can create a custom repository.
|
||||
|
||||
Repositories are created under the `src/repositories` directory of your Medusa backend project. The file name is the name of the repository without `Repository`.
|
||||
|
||||
For example, to create a repository for a `Post` entity, create the file `src/repositories/post.ts` with the following content:
|
||||
|
||||
```ts src=src/repositories/post.ts
|
||||
import { Post } from "../models/post"
|
||||
import {
|
||||
dataSource,
|
||||
} from "@medusajs/medusa/dist/loaders/database"
|
||||
|
||||
export const PostRepository = dataSource
|
||||
.getRepository(Post)
|
||||
.extend({
|
||||
customMethod(): void {
|
||||
// TODO add custom implementation
|
||||
return
|
||||
},
|
||||
})
|
||||
|
||||
export default PostRepository
|
||||
```
|
||||
|
||||
The repository is created using the `getRepository` method of the data source exported from the core package in Medusa. This method accepts the entity as a parameter. You then use the `extend` method to add a new method `customMethod`.
|
||||
|
||||
You can learn about available Repository methods that you can override in [Typeorm's documentation](https://typeorm.io/repository-api).
|
||||
|
||||
:::tip
|
||||
|
||||
A data source is Typeorm’s connection settings that allows you to connect to your database. You can learn more about it in [Typeorm’s documentation](https://typeorm.io/data-source).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Using Custom Repositories in Other Resources
|
||||
|
||||
### API Routes
|
||||
|
||||
To access a custom repository within an API Route, use the `MedusaRequest` object's `scope.resolve` method.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/store/custom/route.ts"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
import { PostRepository } from "../repositories/post"
|
||||
import { EntityManager } from "typeorm"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const postRepository: typeof PostRepository =
|
||||
req.scope.resolve("postRepository")
|
||||
const manager: EntityManager = req.scope.resolve("manager")
|
||||
const postRepo = manager.withRepository(postRepository)
|
||||
|
||||
return res.json({
|
||||
posts: await postRepo.find(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
You can learn more about API Route [here](../api-routes/overview.mdx).
|
||||
|
||||
### Services
|
||||
|
||||
As custom repositories are registered in the [dependency container](../fundamentals/dependency-injection.md#dependency-container-and-injection), they can be accessed through dependency injection in the constructor of a service.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/services/post.ts"
|
||||
import { PostRepository } from "../repositories/post"
|
||||
|
||||
class PostService extends TransactionBaseService {
|
||||
// ...
|
||||
protected postRepository_: typeof PostRepository
|
||||
|
||||
constructor(container) {
|
||||
super(container)
|
||||
// ...
|
||||
this.postRepository_ = container.postRepository
|
||||
}
|
||||
|
||||
async list(): Promise<Post[]> {
|
||||
const postRepo = this.activeManager_.withRepository(
|
||||
this.postRepository_
|
||||
)
|
||||
return await postRepo.find()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
You can learn more about services [here](../services/overview.mdx).
|
||||
|
||||
### Subscribers
|
||||
|
||||
A subscriber handler function can resolve a repository using the `container` property of its parameter. The `container` has a method `resolve` which accepts the registration name of the repository as a parameter.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/subscribers/post-handler.ts"
|
||||
import {
|
||||
type SubscriberArgs,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
import { PostRepository } from "../repositories/post"
|
||||
|
||||
export default async function postHandler({
|
||||
data, eventName, container, pluginOptions,
|
||||
}: SubscriberArgs) {
|
||||
const postRepository: PostRepository = container.resolve(
|
||||
"postRepository"
|
||||
)
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
// ...
|
||||
```
|
||||
|
||||
### Other Resources
|
||||
|
||||
Resources that have access to the dependency container can access repositories just like any other resources. You can learn more about the dependency container and dependency injection in [this documentation](../fundamentals/dependency-injection.md).
|
||||
|
||||
---
|
||||
|
||||
## Common Methods
|
||||
|
||||
This section covers some common methods and use cases you'll use with repositories. You can refer to [Typeorm's documentation](https://typeorm.io/repository-api) for full details on available methods.
|
||||
|
||||
### Retrieving a List of Records
|
||||
|
||||
To retrieve a list of records of an entity, use the `find` method:
|
||||
|
||||
```ts
|
||||
const posts = await postRepository.find()
|
||||
```
|
||||
|
||||
#### Pass Filters
|
||||
|
||||
You can also filter the retrieved items by passing an object of type [FindOption](https://typeorm.io/find-options) as a first parameter:
|
||||
|
||||
```ts
|
||||
const posts = await postRepository.find({
|
||||
where: {
|
||||
id: "1",
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
#### Configure Pagination
|
||||
|
||||
In addition, you can pass `skip` and `take` properties to the object for pagination purposes. `skip`'s value is a number that indicates how many items to skip before retrieving the results, and `take` indicates how many items to return:
|
||||
|
||||
```ts
|
||||
const posts = await postRepository.find({
|
||||
skip: 0,
|
||||
take: 20,
|
||||
})
|
||||
```
|
||||
|
||||
#### Expand Relations
|
||||
|
||||
To expand relations and retrieve them as part of each item in the result, you can pass the `relations` property to the parameter object:
|
||||
|
||||
```ts
|
||||
const posts = await postRepository.find({
|
||||
relations: ["authors"],
|
||||
})
|
||||
```
|
||||
|
||||
To expand nested relations (a relation of another relation), use dot notation:
|
||||
|
||||
```ts
|
||||
const posts = await postRepository.find({
|
||||
relations: ["authors.posts"],
|
||||
})
|
||||
```
|
||||
|
||||
#### buildQuery Utility Method
|
||||
|
||||
Medusa provides a utility method `buildQuery` that allows you to easily format the object to pass to the `find` method. `buildQuery` accepts two parameters:
|
||||
|
||||
1. The first parameter is an object whose keys are the attributes of the entity, and their values are the value to filter by.
|
||||
2. The second parameter includes the options related to pagination (such as `skip` and `take`), the relations to expand, and fields to select in each returned item.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
import { buildQuery } from "@medusajs/medusa"
|
||||
|
||||
// ...
|
||||
const selector = {
|
||||
id: "1",
|
||||
}
|
||||
|
||||
const config = {
|
||||
skip: 0,
|
||||
take: 20,
|
||||
relations: ["authors"],
|
||||
select: ["title"],
|
||||
}
|
||||
|
||||
const query = buildQuery(selector, config)
|
||||
|
||||
const posts = await postRepository.find(query)
|
||||
```
|
||||
|
||||
### Retrieving a List of Records with Count
|
||||
|
||||
You can retrieve a list of records along with their count using the `findAndCount` method:
|
||||
|
||||
```ts
|
||||
const [posts, count] = await postRepository.findAndCount()
|
||||
```
|
||||
|
||||
This method also accepts the same options object as a parameter similar to the [find](#retrieving-a-list-of-records) method.
|
||||
|
||||
### Retrieving a Single Record
|
||||
|
||||
You can retrieve one record of an entity using the `findOne` method:
|
||||
|
||||
```ts
|
||||
const post = await postRepository.findOne({
|
||||
where: {
|
||||
id: "1",
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
If the record does not exist, `null` will be returned instead.
|
||||
|
||||
You can also pass the method an options object similar to the [find](#retrieving-a-list-of-records) method to expand relations or specify what fields to select.
|
||||
|
||||
### Create Record
|
||||
|
||||
To create a new record of an entity, use the `create` and `save` methods of the repository:
|
||||
|
||||
```ts
|
||||
const post = postRepository.create()
|
||||
post.title = data.title
|
||||
post.author_id = data.author_id
|
||||
const result = await postRepository.save(post)
|
||||
```
|
||||
|
||||
The `save` method is what actually persists the created record in the database.
|
||||
|
||||
### Update Record
|
||||
|
||||
To update a record of an entity, use the `save` method of the repository:
|
||||
|
||||
```ts
|
||||
// const data = {
|
||||
// title: ''
|
||||
// }
|
||||
|
||||
// const post = await postRepository.findOne({
|
||||
// where: {
|
||||
// id: '1'
|
||||
// }
|
||||
// })
|
||||
|
||||
Object.assign(post, data)
|
||||
|
||||
const updatedPost = await postRepository.save(post)
|
||||
```
|
||||
|
||||
### Delete a Record
|
||||
|
||||
To delete a record of an entity, use the `remove` method of the repository:
|
||||
|
||||
```ts
|
||||
const post = await postRepository.findOne({
|
||||
where: {
|
||||
id: "1",
|
||||
},
|
||||
})
|
||||
|
||||
await postRepository.remove([post])
|
||||
```
|
||||
|
||||
This method accepts an array of records to delete.
|
||||
|
||||
### Soft Delete a Record
|
||||
|
||||
If an entity extends the `SoftDeletableEntity` class, it can be soft deleted. This means that the entity won't be fully deleted from the database, but it can't be retrieved as a non-deleted entity would.
|
||||
|
||||
To soft-delete a record of an entity, use the `softRemove` method:
|
||||
|
||||
```ts
|
||||
const post = await postRepository.findOne({
|
||||
where: {
|
||||
id: "1",
|
||||
},
|
||||
})
|
||||
|
||||
await postRepository.softRemove([post])
|
||||
```
|
||||
|
||||
You can later retrieve that entity by passing the `withDeleted` option to methods like `find`, `findAndCount`, or `findOne`:
|
||||
|
||||
```ts
|
||||
const posts = await postRepository.find({
|
||||
withDeleted: true,
|
||||
})
|
||||
```
|
||||
@@ -1,260 +0,0 @@
|
||||
---
|
||||
description: 'In this document, you’ll learn how to create an events module, then test and publish the module as an NPM package.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# Create Events Module
|
||||
|
||||
In this document, you’ll learn how to create an events module.
|
||||
|
||||
## Overview
|
||||
|
||||
Medusa provides ready-made modules for events, including the local and Redis modules. If you prefer another technology used for managing events, you can build a module locally and use it in your Medusa backend. You can also publish to NPM and reuse it across multiple Medusa backend instances.
|
||||
|
||||
In this document, you’ll learn how to build your own Medusa events module, mainly focusing on creating the event bus service and the available methods you need to implement within your module.
|
||||
|
||||
---
|
||||
|
||||
## (Optional) Step 0: Prepare Module Directory
|
||||
|
||||
Before you start implementing your module, it's recommended to prepare the directory or project holding your custom implementation.
|
||||
|
||||
You can refer to the [Project Preparation step in the Create Module documentation](../modules/create.mdx#optional-step-0-project-preparation) to learn how to do that.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create the Service
|
||||
|
||||
Create the file `services/event-bus-custom.ts` which will hold your event bus service. Note that the name of the file is recommended to be in the format `event-bus-<service_name>` where `<service_name>` is the name of the service you’re integrating. For example, `event-bus-redis`.
|
||||
|
||||
Add the following content to the file:
|
||||
|
||||
```ts title="services/event-bus-custom.ts"
|
||||
import { EmitData, EventBusTypes } from "@medusajs/types"
|
||||
import { AbstractEventBusModuleService } from "@medusajs/utils"
|
||||
|
||||
class CustomEventBus extends AbstractEventBusModuleService {
|
||||
async emit<T>(
|
||||
eventName: string,
|
||||
data: T,
|
||||
options: Record<string, unknown>
|
||||
): Promise<void>;
|
||||
async emit<T>(data: EmitData<T>[]): Promise<void>;
|
||||
async emit(
|
||||
eventName: unknown,
|
||||
data?: unknown,
|
||||
options?: unknown
|
||||
): Promise<void> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
}
|
||||
|
||||
export default CustomEventBus
|
||||
```
|
||||
|
||||
This creates the class `CustomEventBus` that implements the `AbstractEventBusModuleService` class imported from `@medusajs/utils`. Feel free to rename the class to what’s relevant for your event bus service.
|
||||
|
||||
In the class you must implement the `emit` method. You can optionally implement the `subscribe` and `unsubscribe` methods.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Implement Methods
|
||||
|
||||
### Note About the eventToSubscribersMap Property
|
||||
|
||||
The `AbstractEventBusModuleService` implements two methods for handling subscription: `subscribe` and `unsubscribe`. In these methods, the subscribed handler functions are managed within a class property `eventToSubscribersMap`, which is a JavaScript Map. The map's keys are the event names, whereas the value of each key is an array of subscribed handler functions.
|
||||
|
||||
In your custom implementation, you can use this property to manage the subscribed handler functions. For example, you can get the subscribers of a method using the `get` method of the map:
|
||||
|
||||
```ts
|
||||
const eventSubscribers =
|
||||
this.eventToSubscribersMap.get(eventName) || []
|
||||
```
|
||||
|
||||
Alternatively, you can implement custom logic for the `subscribe` and `unsubscribe` events, which is explained later in this guide.
|
||||
|
||||
### constructor
|
||||
|
||||
The `constructor` method of a service allows you to prepare any third-party client or service necessary to be used in other methods. It also allows you to get access to the module’s options which are typically defined in `medusa-config.js`, and to other services and resources in the Medusa backend using [dependency injection](../fundamentals/dependency-injection.md).
|
||||
|
||||
Here’s an example of how you can use the `constructor` to store the options of your module:
|
||||
|
||||
<!-- eslint-disable prefer-rest-params -->
|
||||
|
||||
```ts title="services/event-bus-custom.ts"
|
||||
class CustomEventBus extends AbstractEventBusModuleService {
|
||||
protected readonly moduleOptions: Record<string, any>
|
||||
|
||||
constructor({
|
||||
// inject resources from the Medusa backend
|
||||
// for example, you can inject the logger
|
||||
logger,
|
||||
}, options) {
|
||||
super(...arguments)
|
||||
this.moduleOptions = options
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### emit
|
||||
|
||||
The `emit` method is used to push an event from Medusa into your messaging system. Typically, the subscribers to that event would then pick up the message and execute their asynchronous tasks.
|
||||
|
||||
The `emit` method has two different signatures:
|
||||
|
||||
1. The first signature accepts three parameters. The first parameter is `eventName` being a required string indicating the name of the event to trigger. The second parameter is `data` being the optional data to send to subscribers of that event. The third optional parameter `options` which can be used to pass options specific to the event bus.
|
||||
2. The second signature accepts one parameter, which is an array of objects having three properties: `eventName`, `data`, and `options`. These are the same as the parameters that can be passed in the first signature. This signature allows emitting more than one event.
|
||||
|
||||
The `options` parameter depends on the event bus integration. For example, the Redis event bus accepts the following options:
|
||||
|
||||
```ts title="services/event-bus-custom.ts"
|
||||
type JobData<T> = {
|
||||
eventName: string
|
||||
data: T
|
||||
completedSubscriberIds?: string[] | undefined
|
||||
}
|
||||
```
|
||||
|
||||
You can implement your method in a way that supports both signatures by checking the type of the first input. For example:
|
||||
|
||||
```ts title="services/event-bus-custom.ts"
|
||||
class CustomEventBus extends AbstractEventBusModuleService {
|
||||
// ...
|
||||
async emit<T>(
|
||||
eventName: string,
|
||||
data: T,
|
||||
options: Record<string, unknown>
|
||||
): Promise<void>;
|
||||
async emit<T>(data: EmitData<T>[]): Promise<void>;
|
||||
async emit<T>(
|
||||
eventOrData: string | EmitData<T>[],
|
||||
data?: T,
|
||||
options: Record<string, unknown> = {}
|
||||
): Promise<void> {
|
||||
const isBulkEmit = Array.isArray(eventOrData)
|
||||
|
||||
// emit event
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### (optional) subscribe
|
||||
|
||||
As mentioned earlier, this method is already implemented in the `AbstractEventBusModuleService` class. This section explains how you can implement your custom subscribe logic if necessary.
|
||||
|
||||
The `subscribe` method attaches a handler function to the specified event, which is run when the event is triggered. It is typically used inside a subscriber class.
|
||||
|
||||
The `subscribe` method accepts three parameters:
|
||||
|
||||
1. The first parameter `eventName` is a required string. It indicates which event the handler function is subscribing to.
|
||||
2. The second parameter `subscriber` is a required function that performs an action when the event is triggered.
|
||||
3. The third parameter `context` is an optional object that has the property `subscriberId`. Subscriber IDs are useful to differentiate between handler functions when retrying a failed method. It’s also useful for unsubscribing an event handler. Note that if you must implement the mechanism around assigning IDs to subscribers when you override the `subscribe` method.
|
||||
|
||||
The implementation of this method depends on the service you’re using for the event bus:
|
||||
|
||||
```ts title="services/event-bus-custom.ts"
|
||||
class CustomEventBus extends AbstractEventBusModuleService {
|
||||
// ...
|
||||
subscribe(
|
||||
eventName: string | symbol,
|
||||
subscriber: EventBusTypes.Subscriber,
|
||||
context?: EventBusTypes.SubscriberContext): this {
|
||||
// TODO implement subscription
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### (optional) unsubscribe
|
||||
|
||||
As mentioned earlier, this method is already implemented in the `AbstractEventBusModuleService` class. This section explains how you can implement your custom unsubscribe logic if necessary.
|
||||
|
||||
The `unsubscribe` method is used to unsubscribe a handler function from an event.
|
||||
|
||||
The `unsubscribe` method accepts three parameters:
|
||||
|
||||
1. The first parameter `eventName` is a required string. It indicates which event the handler function is unsubscribing from.
|
||||
2. The second parameter `subscriber` is a required function that was initially subscribed to the event.
|
||||
3. The third parameter `context` is an optional object that has the property `subscriberId`. It can be used to specify the ID of the subscriber to unsubscribe.
|
||||
|
||||
The implementation of this method depends on the service you’re using for the event bus:
|
||||
|
||||
```ts title="services/event-bus-custom.ts"
|
||||
class CustomEventBus extends AbstractEventBusModuleService {
|
||||
// ...
|
||||
unsubscribe(
|
||||
eventName: string | symbol,
|
||||
subscriber: EventBusTypes.Subscriber,
|
||||
context?: EventBusTypes.SubscriberContext): this {
|
||||
// TODO implement subscription
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Export the Service
|
||||
|
||||
After implementing the event bus service, you must export it so that the Medusa backend can use it.
|
||||
|
||||
Create the file `index.ts` with the following content:
|
||||
|
||||
```ts title="services/event-bus-custom.ts"
|
||||
import { ModuleExports } from "@medusajs/modules-sdk"
|
||||
|
||||
import { CustomEventBus } from "./services"
|
||||
|
||||
const service = CustomEventBus
|
||||
|
||||
const moduleDefinition: ModuleExports = {
|
||||
service,
|
||||
}
|
||||
|
||||
export default moduleDefinition
|
||||
```
|
||||
|
||||
This exports a module definition, which requires at least a `service`. If you named your service something other than `CustomEventBus`, make sure to replace it with that.
|
||||
|
||||
You can learn more about what other properties you can export in your module definition in the [Create a Module documentation](../modules/create.mdx#step-2-export-module).
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Test your Module
|
||||
|
||||
You can test your module in the Medusa backend by referencing it in the configurations.
|
||||
|
||||
To do that, add the module to the exported configuration in `medusa-config.js` as follows:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
// ...
|
||||
modules: {
|
||||
// ...
|
||||
eventBus: {
|
||||
resolve: "path/to/custom-module",
|
||||
options: {
|
||||
// any necessary options
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Make sure to replace the `path/to/custom-module` with a relative path from your Medusa backend to your module. You can learn more about module reference in the [Create Module documentation](../modules/create.mdx#module-reference).
|
||||
|
||||
You can also add any necessary options to the module.
|
||||
|
||||
Then, to test the module, run the Medusa backend which also runs your module:
|
||||
|
||||
```bash npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## (Optional) Step 5: Publish your Module
|
||||
|
||||
You can publish your events module to NPM. This can be useful if you want to reuse your module across Medusa backend instances, or want to allow other developers to use it.
|
||||
|
||||
You can refer to the [Publish Module documentation](../modules/publish.md) to learn how to publish your module.
|
||||
@@ -1,140 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to create a subscriber in Medusa. You can use subscribers to implement functionalities like sending an order confirmation email.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# (Deprecated) How to Create a Subscriber
|
||||
|
||||
In this document, you’ll learn how to create a [Subscriber](./subscribers.mdx) in Medusa that listens to events to perform an action.
|
||||
|
||||
:::note
|
||||
|
||||
Following v1.18 of `@medusajs/medusa`, the approach in this guide is deprecated. It's recommended to follow [this guide](./create-subscriber.md) instead.
|
||||
|
||||
:::
|
||||
|
||||
## Implementation
|
||||
|
||||
A subscriber is a TypeScript or JavaScript file that is created under `src/subscribers`. Its file name, by convention, should be the class name of the subscriber without the word `Subscriber`. For example, if the subscriber is `HelloSubscriber`, the file name should be `hello.ts`.
|
||||
|
||||
After creating the file under `src/subscribers`, in the constructor of your subscriber, listen to events using `eventBusService.subscribe` , where `eventBusService` is a service injected into your subscriber’s constructor.
|
||||
|
||||
The `eventBusService.subscribe` method receives the name of the event as a first parameter and as a second parameter a method in your subscriber that will handle this event.
|
||||
|
||||
For example, here is the `OrderNotifierSubscriber` class created in `src/subscribers/order-notifier.ts`:
|
||||
|
||||
```ts title="src/subscribers/order-notifier.ts"
|
||||
class OrderNotifierSubscriber {
|
||||
constructor({ eventBusService }) {
|
||||
eventBusService.subscribe("order.placed", this.handleOrder)
|
||||
}
|
||||
|
||||
handleOrder = async (data) => {
|
||||
console.log("New Order: " + data.id)
|
||||
}
|
||||
}
|
||||
|
||||
export default OrderNotifierSubscriber
|
||||
```
|
||||
|
||||
This subscriber registers the method `handleOrder` as one of the handlers of the `order.placed` event. The method `handleOrder` will be executed every time an order is placed. It receives the order ID in the `data` parameter. You can then use the order’s details to perform any kind of task you need.
|
||||
|
||||
:::tip
|
||||
|
||||
For the `order.placed` event, the `data` object won't contain other order data. Only the ID of the order. You can retrieve the order information using the `orderService`.
|
||||
|
||||
:::
|
||||
|
||||
### Subscriber ID
|
||||
|
||||
The `subscribe` method of the `eventBusService` accepts a third optional parameter which is a context object. This object has a property `subscriberId` with its value being a string. This ID is useful when there is more than one handler method attached to a single event or if you have multiple Medusa backends running. This allows the events bus service to differentiate between handler methods when retrying a failed one.
|
||||
If a subscriber ID is not passed on subscription, all handler methods are run again. This can lead to data inconsistencies or general unwanted behavior in your system. On the other hand, if you want all handler methods to run again when one of them fails, you can omit passing a subscriber ID.
|
||||
|
||||
An example of using the subscribe method with the third parameter:
|
||||
|
||||
```ts
|
||||
eventBusService.subscribe("order.placed", this.handleOrder, {
|
||||
subscriberId: "my-unique-subscriber",
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Medusa Configurations
|
||||
|
||||
Within your subscriber, you may need to access the Medusa configuration exported from `medusa-config.js`. To do that, you can access `configModule` using dependency injection.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
import { ConfigModule, EventBusService } from "@medusajs/medusa"
|
||||
|
||||
type InjectedDependencies = {
|
||||
eventBusService: EventBusService
|
||||
configModule: ConfigModule
|
||||
}
|
||||
|
||||
class OrderNotifierSubscriber {
|
||||
protected readonly configModule_: ConfigModule
|
||||
|
||||
constructor({
|
||||
eventBusService,
|
||||
configModule,
|
||||
}: InjectedDependencies) {
|
||||
this.configModule_ = configModule
|
||||
eventBusService.subscribe("order.placed", this.handleOrder)
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default OrderNotifierSubscriber
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Using Services in Subscribers
|
||||
|
||||
You can access any service through the dependencies injected to your subscriber’s constructor.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/subscribers/order-notifier.ts"
|
||||
class OrderNotifierSubscriber {
|
||||
constructor({ productService, eventBusService }) {
|
||||
this.productService = productService
|
||||
|
||||
eventBusService.subscribe(
|
||||
"order.placed",
|
||||
this.handleOrder
|
||||
)
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
You can then use `this.productService` anywhere in your subscriber’s methods. For example:
|
||||
|
||||
```ts title="src/subscribers/order-notifier.ts"
|
||||
class OrderNotifierSubscriber {
|
||||
// ...
|
||||
handleOrder = async (data) => {
|
||||
// ...
|
||||
const product = this.productService.list()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
When using attributes defined in the subscriber, such as the `productService` in the example above, you must use an arrow function to declare the method. Otherwise, the attribute will be undefined when used.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Example: send order confirmation email](../../modules/orders/backend/send-order-confirmation.md)
|
||||
- [Example: send registration confirmation email](../../modules/customers/backend/send-confirmation.md)
|
||||
- [Create a Plugin](../plugins/create.mdx)
|
||||
@@ -1,142 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to create a subscriber in Medusa. You can use subscribers to implement functionalities like sending an order confirmation email.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Create a Subscriber
|
||||
|
||||
In this document, you’ll learn how to create a [Subscriber](./subscribers.mdx) in Medusa that listens to events to perform an action.
|
||||
|
||||
:::tip
|
||||
|
||||
v1.18 of `@medusajs/medusa` introduced a new approach to create a subscriber. If you're looking for the old guide, you can find it [here](./create-subscriber-deprecated.md). However, it's highly recommended you follow this new approach, as the old one is deprecated.
|
||||
|
||||
:::
|
||||
|
||||
## Implementation
|
||||
|
||||
A subscriber is a TypeScript or JavaScript file that is created under `src/subscribers`. It can be created under subdirectories of `src/subscribers` as well. For example, you can place all subscribers to product events under the `src/subscribers/products` directory.
|
||||
|
||||
The subscriber file exports a default handler function, and the subscriber's configurations.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/subscribers/product-update-handler.ts"
|
||||
import {
|
||||
ProductService,
|
||||
type SubscriberConfig,
|
||||
type SubscriberArgs,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export default async function productUpdateHandler({
|
||||
data, eventName, container, pluginOptions,
|
||||
}: SubscriberArgs<Record<string, any>>) {
|
||||
const productService: ProductService = container.resolve(
|
||||
"productService"
|
||||
)
|
||||
|
||||
const { id } = data
|
||||
|
||||
const product = await productService.retrieve(id)
|
||||
|
||||
// do something with the product...
|
||||
}
|
||||
|
||||
export const config: SubscriberConfig = {
|
||||
event: ProductService.Events.UPDATED,
|
||||
context: {
|
||||
subscriberId: "product-update-handler",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Subscriber Configuration
|
||||
|
||||
The exported configuration object of type `SubscriberConfig` must include the following properties:
|
||||
|
||||
- `event`: A string or an array of strings, each being the name of the event that the subscriber handler function listens to.
|
||||
- `context`: An object that defines the context of the subscriber. It can accept any properties along with the `subscriberId` property. Learn more about the subscriber ID and the context object in [this section](#context-with-subscriber-id).
|
||||
|
||||
### Subscriber Handler Function
|
||||
|
||||
The default-export of the subscriber file is a handler function that is executed when the events specified in the exported configuration is triggered.
|
||||
|
||||
The function accepts a parameter of type `SubscriberArgs`, which has the following properties:
|
||||
|
||||
- `data`: The data payload of the emitted event. Its type is different for each event. So, make sure to check the [events reference](./events-list.md) for the expected payload of the events your subscriber listens to. You can then pass the expected payload type as a type parameter to `SubscriberArgs`, for example, `Record<string, string>`.
|
||||
- `eventName`: A string indicating the name of the event. This is useful if your subscriber listens to more than one event and you want to differentiate between them.
|
||||
- `container`: The [dependency container](../fundamentals/dependency-injection.md) that allows you to resolve Medusa resources, such as services.
|
||||
- `pluginOptions`: When the subscriber is created within a plugin, this object holds the plugin's options defined in the [Medusa configurations](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx).
|
||||
|
||||
---
|
||||
|
||||
## Context with Subscriber ID
|
||||
|
||||
The `context` property of the subscriber configuration object is passed to the `eventBusService`. You can pass the `subscriberId` and any custom data in it.
|
||||
|
||||
:::note
|
||||
|
||||
The subscriber ID is useful when there is more than one handler function attached to a single event or if you have multiple Medusa backends running. This allows the events bus service to differentiate between handler functions when retrying a failed one, avoiding retrying all subscribers which can lead to data inconsistencies or general unwanted behavior in your system.
|
||||
|
||||
:::
|
||||
|
||||
### Inferred Subscriber ID
|
||||
|
||||
If you don't pass a subscriber ID to the subscriber configurations, the name of the subscriber function is used as the subscriber ID. If the subscriber function is an anonymous function, the name of the subscriber file is used instead.
|
||||
|
||||
---
|
||||
|
||||
## Caveats for Local Event Bus
|
||||
|
||||
If you use the `event-bus-local` as your event bus service, note the following:
|
||||
|
||||
- The `subscriberId` passed in the context is overwritten to a random ID when using `event-bus-local`. So, setting the subscriber ID in the context won't have any effect in this case.
|
||||
- The `eventName` passed to the handler function will be `undefined` when using `event-bus-local` as it doesn't pass the event name properly.
|
||||
|
||||
:::note
|
||||
|
||||
While the local event bus is a good option for development, it's highly recommended to use the [Redis Event Module](./modules/redis.md) in production.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Medusa Configurations
|
||||
|
||||
Within your subscriber, you may need to access the Medusa configuration exported from `medusa-config.js`. To do that, you can access `configModule` using dependency injection.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/subscribers/product-update-handler.ts"
|
||||
import {
|
||||
ProductService,
|
||||
type SubscriberConfig,
|
||||
type SubscriberArgs,
|
||||
type ConfigModule,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export default async function productUpdateHandler({
|
||||
data, eventName, container, pluginOptions,
|
||||
}: SubscriberArgs) {
|
||||
const configModule: ConfigModule = container.resolve(
|
||||
"configModule"
|
||||
)
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export const config: SubscriberConfig = {
|
||||
event: ProductService.Events.UPDATED,
|
||||
context: {
|
||||
subscriberId: "product-update-handler",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Example: send order confirmation email](../../modules/orders/backend/send-order-confirmation.md)
|
||||
- [Example: send registration confirmation email](../../modules/customers/backend/send-confirmation.md)
|
||||
- [Create a Plugin](../plugins/create.mdx)
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,131 +0,0 @@
|
||||
---
|
||||
description: 'Learn how the events system is implemented in Medusa. It is built on a publish-subscribe architecture. The Medusa core publishes events when certain actions take place.'
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Events
|
||||
|
||||
In this document, you’ll learn what events are and why they’re useful in Medusa.
|
||||
|
||||
## Overview
|
||||
|
||||
Events are used in Medusa to inform different parts of the commerce ecosystem that this event occurred. For example, when an order is placed, the `order.placed` event is triggered, which informs notification services like SendGrid to send a confirmation email to the customer.
|
||||
|
||||
:::tip
|
||||
|
||||
If you want to implement order confirmation emails, you can check this [step-by-step guide](../../modules/orders/backend/send-order-confirmation.md).
|
||||
|
||||
:::
|
||||
|
||||
The events system in Medusa is built on a publish/subscribe architecture. The Medusa core publish an event when an action takes place, and modules, plugins, or other forms of customizations can subscribe to that event. [Subscribers](./subscribers.mdx) can then perform a task asynchronously when the event is triggered.
|
||||
|
||||
Although the core implements the main logic behind the events system, you’ll need to use an event module that takes care of the publish/subscribe functionality such as subscribing and emitting events. Medusa provides modules that you can use both for development and production, including Redis and Local modules.
|
||||
|
||||
---
|
||||
|
||||
## Database Transactions
|
||||
|
||||
Transactions in Medusa ensure Atomicity, Consistency, Isolation, and Durability (ACID) guarantees for operations in the Medusa core.
|
||||
|
||||
In many cases, services typically update resources in the database and emit an event within a transactional operation. To ensure that these events don't cause data inconsistencies (for example, a plugin subscribes to an event to contact a third-party service, but the transaction fails) the concept of a staged job is introduced.
|
||||
|
||||
Instead of events being processed immediately, they're stored in the database as a staged job until they're ready. In other words, until the transaction has succeeded.
|
||||
|
||||
This rather complex logic is abstracted away from the consumers of the EventBusService, but here's an example of the flow when an API request is made:
|
||||
|
||||
- API request starts.
|
||||
- Transaction is initiated.
|
||||
- Service layer performs some logic.
|
||||
- Events are emitted and stored in the database for eventual processing.
|
||||
- Transaction is committed.
|
||||
- API request ends.
|
||||
- Events in the database become visible.
|
||||
|
||||
To pull staged jobs from the database, a separate enqueuer polls the database every three seconds to discover new visible jobs. These jobs are then added to the queue and processed as described in the Processing section earlier.
|
||||
|
||||
---
|
||||
|
||||
## Emitting Events
|
||||
|
||||
You can emit events in Medusa using the `EventBusService`. For example:
|
||||
|
||||
```ts
|
||||
this.eventBusService.emit("custom-event", {
|
||||
// attach any data to the event
|
||||
})
|
||||
```
|
||||
|
||||
You can also emit more than one event:
|
||||
|
||||
```ts
|
||||
this.eventBusService.emit([
|
||||
{
|
||||
eventName: "custom-event-1",
|
||||
data: {
|
||||
// attach any data to the event
|
||||
},
|
||||
},
|
||||
{
|
||||
eventName: "custom-event-2",
|
||||
data: {
|
||||
// attach any data to the event
|
||||
},
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Available Modules
|
||||
|
||||
Medusa’s default starter project comes with the local event module (`@medusajs/event-bus-local`). For production environments, it’s recommended to use the Redis event module package (`@medusajs/event-bus-redis`) that you can install.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/events/modules/redis',
|
||||
label: 'Redis',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to install Redis events module in Medusa.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/events/modules/local',
|
||||
label: 'Local',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to install local events module in Medusa.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Custom Development
|
||||
|
||||
Developers can create custom event modules, allowing them to integrate any third-party services or logic to handle this functionality. Developers can also create and use subscribers to handle events in Medusa.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/events/create-module',
|
||||
label: 'Create an Event Module',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create an event module.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/events/create-subscriber',
|
||||
label: 'Create a Subscriber',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a subscriber.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
@@ -1,68 +0,0 @@
|
||||
---
|
||||
description: 'In this document, you’ll learn about the local events module and how you can install it in your Medusa backend.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# Local Events Module
|
||||
|
||||
In this document, you’ll learn about the local events module and how you can install it in your Medusa backend.
|
||||
|
||||
## Overview
|
||||
|
||||
Medusa’s modular architecture allows developers to extend or completely replace the logic used for events. You can create a custom module, or you can use the modules Medusa provides.
|
||||
|
||||
One of these modules is the local events module. This module allows you to utilize Node EventEmitter for the events system in Medusa. The Node EventEmitter is limited to a single process environment. This module is useful for development and testing, but it’s recommended to use the [Redis events module](./redis.md) in production.
|
||||
|
||||
This document will you guide you through installing the local events module.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
It’s assumed you already have a Medusa backend installed. If not, you can learn how to install it by following [this guide](../../backend/install.mdx).
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Install the Module
|
||||
|
||||
In the root directory of your Medusa backend, install the local events module with the following command:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install @medusajs/event-bus-local
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Add Configuration
|
||||
|
||||
In `medusa-config.js`, add the following to the exported object:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
// ...
|
||||
modules: {
|
||||
// ...
|
||||
eventBus: {
|
||||
resolve: "@medusajs/event-bus-local",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
This registers the local events module as the main events service to use. This module does not require any options.
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Test Module
|
||||
|
||||
To test the module, run the following command to start the Medusa backend:
|
||||
|
||||
```bash npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
If the module was installed successfully, you should see the following message in the logs:
|
||||
|
||||
```bash noCopy noReport
|
||||
Local Event Bus installed. This is not recommended for production.
|
||||
```
|
||||
@@ -1,95 +0,0 @@
|
||||
---
|
||||
description: 'In this document, you’ll learn about the Redis events module and how you can install it in your Medusa backend.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# Redis Events Module
|
||||
|
||||
In this document, you’ll learn about the Redis events module and how you can install it in your Medusa backend.
|
||||
|
||||
## Overview
|
||||
|
||||
Medusa’s modular architecture allows developers to extend or completely replace the logic used for events. You can create a custom module, or you can use the modules Medusa provides.
|
||||
|
||||
One of these modules is the Redis Module. This module allows you to utilize Redis for the event bus functionality. When installed, the Medusa’s events system is powered by BullMQ and `io-redis`. BullMQ is responsible for the message queue and worker, and `io-redis` is the underlying Redis client that BullMQ connects to for events storage.
|
||||
|
||||
This document will you guide you through installing the Redis Module.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Backend
|
||||
|
||||
It’s assumed you already have a Medusa backend installed. If not, you can learn how to install it by following [this guide](../../backend/install.mdx).
|
||||
|
||||
### Redis
|
||||
|
||||
You must have Redis installed and configured in your Medusa backend. You can learn how to install Redis in [their documentation](https://redis.io/docs/getting-started/installation/).
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Install the Module
|
||||
|
||||
In the root directory of your Medusa backend, install the Redis events module with the following command:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install @medusajs/event-bus-redis
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Add Environment Variable
|
||||
|
||||
The Redis events module requires a connection URL to Redis as part of its options. If you don’t already have an environment variable set for a Redis URL, make sure to add one:
|
||||
|
||||
```bash
|
||||
EVENTS_REDIS_URL=<YOUR_REDIS_URL>
|
||||
```
|
||||
|
||||
Where `<YOUR_REDIS_URL>` is a connection URL to your Redis instance.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Add Configuration
|
||||
|
||||
In `medusa-config.js`, add the following to the exported object:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
// ...
|
||||
modules: {
|
||||
// ...
|
||||
eventBus: {
|
||||
resolve: "@medusajs/event-bus-redis",
|
||||
options: {
|
||||
redisUrl: process.env.EVENTS_REDIS_URL,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
This registers the Redis events module as the main event bus service to use. In the options, you pass `redisUrl` with the value being the environment variable you set. This is the only required option.
|
||||
|
||||
Other available options include:
|
||||
|
||||
- `queueName`: a string indicating the name of the BullMQ queue. By default, it’s `events-queue`.
|
||||
- `queueOptions`: an object containing options for the BullMQ queue. You can learn about available options in [BullMQ’s documentation](https://api.docs.bullmq.io/interfaces/QueueOptions.html). By default, it’s an empty object.
|
||||
- `redisOptions`: an object containing options for the Redis instance. You can learn about available options in [io-redis’s documentation](https://redis.github.io/ioredis/index.html#RedisOptions). By default, it’s an empty object.
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Test Module
|
||||
|
||||
To test the module, run the following command to start the Medusa backend:
|
||||
|
||||
```bash npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
If the module was installed successfully, you should see the following message in the logs:
|
||||
|
||||
```bash noCopy noReport
|
||||
Connection to Redis in module 'event-bus-redis' established
|
||||
```
|
||||
@@ -1,45 +0,0 @@
|
||||
---
|
||||
description: 'Learn what subscribers are in Medusa. Subscribers are used to listen to triggered events to perform an action.'
|
||||
---
|
||||
|
||||
import DocCard from '@theme/DocCard';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Subscribers
|
||||
|
||||
In this document, you'll learn what Subscribers are in Medusa.
|
||||
|
||||
## What are Subscribers
|
||||
|
||||
Subscribers register handlers for an events and allows you to perform an action when that event occurs. For example, if you want to send your customer an email when they place an order, then you can listen to the `order.placed` event and send the email when the event is emitted.
|
||||
|
||||
Natively in Medusa there are subscribers to handle different events. However, you can also create your own custom subscribers.
|
||||
|
||||
Custom subscribers are TypeScript or JavaScript files in your project's `src/subscribers` directory. Subscriber files must default export a handler function and export a configuration object.
|
||||
|
||||
Whenever an event is emitted, the subscriber’s handler function is executed. The handler function receives as a parameter an object that includes the data payload, among other parameters. For example, if an order is placed, the `order.placed` event is emitted and all the handlers receive the order ID in the `data` object.
|
||||
|
||||
### Example Use Cases
|
||||
|
||||
Subscribers are useful in many use cases, including:
|
||||
|
||||
- Send a confirmation email to the customer when they place an order by subscribing to the `order.placed` event.
|
||||
- Automatically assign new customers to a customer group by subscribing to the `customer.created`.
|
||||
- Handle custom events that you emit
|
||||
|
||||
---
|
||||
|
||||
## Custom Development
|
||||
|
||||
Developers can create custom subscribers in the Medusa backend, a plugin, or in a module.
|
||||
|
||||
<DocCard item={{
|
||||
type: 'link',
|
||||
href: '/development/events/create-subscriber',
|
||||
label: 'Create a Subscriber',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a subscriber in Medusa.'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@@ -1,28 +0,0 @@
|
||||
---
|
||||
description: "Learn what feature flags in Medusa. Feature flags are used in Medusa to guard beta features that aren’t ready for live and production applications."
|
||||
---
|
||||
|
||||
import DocCard from '@theme/DocCard';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Feature Flags
|
||||
|
||||
In this document, you’ll learn what feature flags in Medusa.
|
||||
|
||||
## Introduction
|
||||
|
||||
Feature flags are used in Medusa to guard beta features that aren’t ready for live and production applications. This allows the Medusa team to keep publishing releases more frequently, while also working on necessary future features behind the scenes. To use these beta features, you must enable their feature flags.
|
||||
|
||||
If a feature is guarded by a flag, entities, migrations, API Routes, and other resources associated with that feature are guarded by that flag as well. So, these resources will only be available to use in Medusa if you have enabled the associated feature flag.
|
||||
|
||||
You can view a list of available feature flags that you can toggle in [the Beta Features documentation](../../beta.md).
|
||||
|
||||
<DocCard item={{
|
||||
type: 'link',
|
||||
href: '/development/feature-flags/toggle',
|
||||
label: 'Toggle Feature Flags',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to enable or disabled a feature flag.'
|
||||
}
|
||||
}} />
|
||||
@@ -1,83 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to toggle feature flags in Medusa. This guide explains the steps required to toggle a feature flag.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Toggle Feature Flags
|
||||
|
||||
In this document, you’ll learn about how to toggle feature flags.
|
||||
|
||||
:::info
|
||||
|
||||
If a feature flag is enabled/disabled by default, you don’t need to manually enable/disable it. Only set the feature flag’s value if it’s different than the default.
|
||||
|
||||
:::
|
||||
|
||||
## Enable Feature Flags
|
||||
|
||||
:::warning
|
||||
|
||||
Features guarded by feature flags are experimental and beta features. Enable them with caution.
|
||||
|
||||
:::
|
||||
|
||||
There are two ways to enable a feature flag.
|
||||
|
||||
### Method One: Using Environment Variables
|
||||
|
||||
You can enable a feature by setting the value of its environment variable to `true`. You can find [the name of a feature flag’s environment variable in the Beta Features documentation page](../../beta.md).
|
||||
|
||||
For example, to enable the Tax-Inclusive Pricing beta feature, add the following environment variable:
|
||||
|
||||
```bash
|
||||
MEDUSA_FF_TAX_INCLUSIVE_PRICING=true
|
||||
```
|
||||
|
||||
### Method Two: Using Backend Configurations
|
||||
|
||||
You can enable a feature by using the backend configurations in `medusa-config.js`. You can find [the feature flag’s key in the Beta Features documentation page](../../beta.md) it’s defined in. It is defined under the property `key` in the exported object.
|
||||
|
||||
For example, to enable the Tax-Inclusive Pricing beta feature, add the following to the exported object in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
featureFlags: {
|
||||
tax_inclusive_pricing: true,
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Note About Precedence
|
||||
|
||||
The environment variable’s value has higher precedence over the backend configurations. So, if you use both these methods on your backend, the value of the environment variable will be used.
|
||||
|
||||
For example, if the value of the environment variable is set to `false`, but the value of the feature flag in the backend configurations is set to `true`, the feature flag will take the value of the environment variable and will be disabled.
|
||||
|
||||
### Running Migrations
|
||||
|
||||
As feature flags generally include adding new entities or making changes to entities in the database, you must run the migrations after enabling the feature flag:
|
||||
|
||||
```bash
|
||||
npx medusa migrations run
|
||||
```
|
||||
|
||||
:::info
|
||||
|
||||
You can learn more about migrations in this documentation.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Disable Feature Flags
|
||||
|
||||
Disabling feature flags follows the same process as enabling the feature flags. All you have to do is change the value in the environment variables or the backend configurations to `false`.
|
||||
|
||||
Once you disable a feature flag, all API Routes, entities, services, or other related classes and functionalities are disabled.
|
||||
|
||||
### Revert Migrations
|
||||
|
||||
If you had the feature flag previously enabled, and you want to disable this feature flag completely, you might need to revert the migrations you ran when you enabled it.
|
||||
|
||||
You can follow [this documentation to learn how to revert the last migration you ran](../../cli/reference.mdx#migrations).
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
description: "Learn what a file service is in Medusa. A file service defines how files are stored in the Medusa Backend."
|
||||
---
|
||||
|
||||
import DocCard from '@theme/DocCard';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# File Service
|
||||
|
||||
In this document, you’ll learn what a file service is in Medusa.
|
||||
|
||||
## Introduction
|
||||
|
||||
A file service defines how files are stored in the Medusa Backend. Those files include products’ images and files used to import or export data.
|
||||
|
||||
Medusa Backend includes a default file service that acts as a placeholder, but does not actually perform any storage functionalities. So, you must either install one of the [existing file-service plugins](../../plugins/file-service/index.mdx), such as [MinIO](../../plugins/file-service/minio.md) or [S3](../../plugins/file-service/s3.mdx), or create your own file service if you want to utilize storage functionalities.
|
||||
|
||||
A file service is a TypeScript or JavaScript class that extends the `AbstractFileService` class from the core `@medusajs/medusa` package. By extending this class, the file service must implement the necessary methods that take care of general upload and download functionalities. The Medusa Backend then uses these methods when necessary, for example, when a product image is uploaded.
|
||||
|
||||
---
|
||||
|
||||
## Custom Development
|
||||
|
||||
Developers can create a custom file service with the desired functionality directly within the Medusa Core, in a plugin, or in a module.
|
||||
|
||||
<DocCard item={{
|
||||
type: 'link',
|
||||
href: '/development/file-service/create-file-service',
|
||||
label: 'Create a File Service',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a file service.',
|
||||
}
|
||||
}} />
|
||||
@@ -1,27 +0,0 @@
|
||||
---
|
||||
description: "Learn about Medusa's architecture and get a general overview of how all different tools work together."
|
||||
---
|
||||
|
||||
# Medusa Architecture Overview
|
||||
|
||||
In this document, you'll get an overview of Medusa's architecture to better understand how all resources and tools work together.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
Medusa's core package `@medusajs/medusa` is a Node.js headless server. It combines all the [Commerce Modules](../../modules/overview.mdx) that Medusa provides. Commerce Modules are ecommerce features that can be used as building blocks in an ecommerce ecosystem. Product is an example of a Commerce Module.
|
||||
|
||||

|
||||
|
||||
The backend connects to a database, such as [PostgreSQL](https://www.postgresql.org/), to store the ecommerce store’s data. The tables in that database are represented by [Entities](../entities/overview.mdx), built on top of [Typeorm](https://typeorm.io/). Entities can also be reflected in the database using [Migrations](../entities/migrations/overview.mdx).
|
||||
|
||||
The retrieval, manipulation, and other utility methods related to that entity are created inside a [Service](../services/overview.mdx). Services are TypeScript or JavaScript classes that, along with other resources, can be accessed throughout the Medusa backend through [dependency injection](./dependency-injection.md).
|
||||
|
||||
The backend doesn't have any tightly-coupled frontend. Instead, it exposes [API Routes](../api-routes/overview.mdx) which are REST APIs that frontends such as an admin or a storefront can use to communicate with the backend.
|
||||
|
||||
Medusa also uses an [Events Architecture](../events/index.mdx) to trigger and handle events. Events are triggered when a specific action occurs, such as when an order is placed. To manage this events system, Medusa connects to a service that implements a pub/sub model, such as [Redis](https://redis.io/).
|
||||
|
||||
Events can be handled using [Subscribers](../events/subscribers.mdx). Subscribers are TypeScript or JavaScript files that export a handler function that's executed asynchronously when an event is triggered, and a configuration object defining what events the subscriber listens to.
|
||||
|
||||
You can create any of the resources in the backend’s architecture, such as entities, API Routes, services, and more, as part of your custom development without directly modifying the backend itself. The Medusa backend uses [loaders](../loaders/overview.mdx) to load the backend’s resources, as well as your custom resources and resources in [Plugins](../plugins/overview.mdx).
|
||||
|
||||
You can package your customizations into Plugins to reuse them in different Medusa backends or publish them for others to use. You can also install existing plugins into your Medusa backend.
|
||||
@@ -1,763 +0,0 @@
|
||||
---
|
||||
description: 'Learn what the dependency container is and how to use it in Medusa. Learn also what dependency injection is, and what the resources registered and their names are.'
|
||||
---
|
||||
|
||||
# Dependency Container and Injection
|
||||
|
||||
In this document, you’ll learn what the dependency container is and how you can use it in Medusa with dependency injection.
|
||||
|
||||
## Introduction
|
||||
|
||||
### What is Dependency Injection
|
||||
|
||||
Dependency Injection is the act of delivering the required resources to a class. These resources are the class’s dependencies. This is usually done by passing (or injecting) the dependencies in the constructor of the class.
|
||||
|
||||
Generally, all resources are registered in a container. Then, whenever a class depends on one of these resources, the system retrieves the resources from the container and injects them into the class’s constructor.
|
||||
|
||||
### Medusa’s Dependency Container
|
||||
|
||||
Medusa uses a dependency container to register essential resources of the backend. You can then access these resources in classes and API Routes using the dependency container.
|
||||
|
||||
For example, if you create a custom service, you can access any other service registered in Medusa in your service’s constructor. That includes Medusa’s core services, services defined in plugins, or other services that you create on your backend.
|
||||
|
||||
You can load more than services in your Medusa backend. You can load the Entity Manager, logger instance, and much more.
|
||||
|
||||
### MedusaContainer
|
||||
|
||||
To manage dependency injections, Medusa uses [Awilix](https://github.com/jeffijoe/awilix). Awilix is an NPM package that implements dependency injection in Node.js projects.
|
||||
|
||||
When you run the Medusa backend, a container of the type `MedusaContainer` is created. This type extends the [AwilixContainer](https://github.com/jeffijoe/awilix#the-awilixcontainer-object) object.
|
||||
|
||||
The backend then registers all important resources in the container, which makes them accessible in classes and API Routes.
|
||||
|
||||
---
|
||||
|
||||
## Registered Resources
|
||||
|
||||
The Medusa backend scans the core Medusa package, plugins, and your files in the `dist` directory and registers the following resources:
|
||||
|
||||
:::tip
|
||||
|
||||
The Lifetime column indicates the lifetime of a service. Other resources that aren't services don't have a lifetime, which is indicated with the `-` in the column. You can learn about what a lifetime is in the [Create a Service](../services/create-service.mdx) documentation.
|
||||
|
||||
:::
|
||||
|
||||
<table class="reference-table table-col-4">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
|
||||
Resource
|
||||
|
||||
</th>
|
||||
<th>
|
||||
|
||||
Description
|
||||
|
||||
</th>
|
||||
|
||||
<th>
|
||||
|
||||
Registration Name
|
||||
|
||||
</th>
|
||||
<th>
|
||||
|
||||
Lifetime
|
||||
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
Configurations
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
The configurations that are exported from `medusa-config.js`.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
`configModule`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
\-
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
Services
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Services that extend the `TransactionBaseService` class.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Each service is registered under its camel-case name. For example, the `ProductService` is registered as `productService`.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Core services by default have the `SINGLETON` lifetime. However, some have a different lifetime which is indicated in this table. Custom services, including services in plugins, by default have the `SCOPED` lifetime, unless defined differently within the custom service.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
Entity Manager
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
An instance of Typeorm’s Entity Manager.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
`manager`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
\-
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
Logger
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
An instance of Medusa CLI’s logger. You can use it to log messages to the terminal.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
`logger`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
\-
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
Single Payment Processor
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
An instance of every payment processor that extends the `AbstractPaymentService` or the `AbstractPaymentProcessor` classes.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Every payment processor is registered under two names:
|
||||
|
||||
- Its camel-case name of the processor. For example, the `StripeProviderService` is registered as `stripeProviderService`.
|
||||
- `pp_` followed by its identifier. For example, the `StripeProviderService` is registered as `pp_stripe`.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
By default, it's `SINGLETON` unless defined differently within the payment processor service.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
All Payment Processors
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
An array of all payment processor that extend the `AbstractPaymentService` or `AbstractPaymentProcessor` class.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
`paymentProviders`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
`paymentProviders` is `TRANSIENT`, and each item in it is `SINGLETON`.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
Single Fulfillment Provider
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
An instance of every fulfillment provider that extends the `FulfillmentService` class.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Every fulfillment provider is registered under two names:
|
||||
|
||||
- Its camel-case name. For example, the `WebshipperFulfillmentService` is registered as `webshipperFulfillmentService`.
|
||||
- `fp_` followed by its identifier. For example, the `WebshipperFulfillmentService` is registered as `fp_webshipper`.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
By default, it's `SINGLETON` unless defined differently within the fulfillment provider service.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
All Fulfillment Providers
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
An array of all fulfillment providers that extend the `FulfillmentService` class.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
`fulfillmentProviders`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
`fulfillmentProviders` is `TRANSIENT`, and each item in it is `SINGLETON`.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
Single Notification Provider
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
An instance of every notification provider that extends the `AbstractNotificationService` or the `BaseNotificationService` classes.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Every notification provider is registered under two names:
|
||||
|
||||
- Its camel-case name. For example, the `SendGridService` is registered as `sendGridService`.
|
||||
- `noti_` followed by its identifier. For example, the `SendGridService` is registered as `noti_sendgrid`.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
By default, it's `SINGLETON` unless defined differently within the notification provider service.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
All Notification Providers
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
An array of all notification providers that extend the `AbstractNotificationService` or the `BaseNotificationService` classes.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
`notificationProviders`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
`notificationProviders` is `TRANSIENT`, and each item in it is `SINGLETON`.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
File Service
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
An instance of the class that extends the `FileService` class, if any.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
The file service is registered under two names:
|
||||
|
||||
- Its camel-case name. For example, the `MinioService` is registered as `minioService`.
|
||||
- `fileService`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
By default, it's `SINGLETON` unless defined differently within the file service.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
Search Service
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
An instance of the class that extends the `AbstractSearchService` or the `SearchService` classes, if any.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
The search service is registered under two names:
|
||||
|
||||
- Its camel-case name. For example, the `AlgoliaService` is registered as `algoliaService`.
|
||||
- `searchService`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
By default, it's `SINGLETON` unless defined differently within the search service.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
Single Tax Provider
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
An instance of every tax provider that extends the `AbstractTaxService` class.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
The tax provider is registered under two names:
|
||||
|
||||
- Its camel-case name.
|
||||
- `tp_` followed by its identifier.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
By default, it's `SINGLETON` unless defined differently within the tax provider service.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
All Tax Providers
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
An array of every tax provider that extends the `AbstractTaxService` class.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
`taxProviders`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
`taxProviders` is `TRANSIENT`, and each item in it is `SINGLETON`.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
Oauth Services
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
An instance of every service that extends the `OauthService` class.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Each Oauth Service is registered under its camel-case name followed by `Oauth`.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
By default, it's `SINGLETON` unless defined differently within the Oauth service.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
Feature Flag Router
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
An instance of the `FlagRouter`. This can be used to list feature flags, set a feature flag’s value, or check if they’re enabled.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
`featureFlagRouter`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
\-
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
Redis
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
An instance of the Redis client. If Redis is not configured, a fake Redis client is registered.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
`redisClient`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
\-
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
Single Entity
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
An instance of every entity.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Each entity is registered under its camel-case name followed by Model. For example, the `CustomerGroup` entity is stored under `customerGroupModel`.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
\-
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
All Entities
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
An array of all database entities that is passed to Typeorm when connecting to the database.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
`db_entities`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
\-
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
Repositories
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
An instance of each repository.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Each repository is registered under its camel-case name. For example, `CustomerGroupRepository` is stored under `customerGroupRepository`.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
\-
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
Single Batch Job Strategy
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
An instance of every class extending the `AbstractBatchJobStrategy` class.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Each batch job strategy is registered under three names:
|
||||
|
||||
- Its camel-case name. For example, `ProductImportStrategy` is registered as `productImportStrategy`.
|
||||
- `batch_` followed by its identifier. For example, the `ProductImportStrategy` is registered under `batch_product-import-strategy`.
|
||||
- `batchType_` followed by its batch job type. For example, the `ProductImportStrategy` is registered under `batchType_product-import`.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
\-
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
All Batch Job Strategies
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
An array of all classes extending the `AbstractBatchJobStrategy` abstract class.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
`batchJobStrategies`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
\-
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
Tax Calculation Strategy
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
An instance of the class implementing the `ITaxCalculationStrategy` interface.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
`taxCalculationStrategy`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
\-
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
Cart Completion Strategy
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
An instance of the class extending the `AbstractCartCompletionStrategy` class.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
`cartCompletionStrategy`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
\-
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
Price Selection Strategy
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
An instance of the class implementing the `IPriceSelectionStrategy` interface.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
`priceSelectionStrategy`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
\-
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
Strategies
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
An instance of strategies that aren’t of the specific types mentioned above and that are under the `strategies` directory.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Its camel-case name.
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
\-
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
---
|
||||
|
||||
## Resolve Resources
|
||||
|
||||
This section covers how to resolve resources from the dependency container to use them in API Routes and classes in general.
|
||||
|
||||
### In API Routes
|
||||
|
||||
To resolve resources, such as services, in API Routes, use the `MedusaRequest` object's `scope.resolve` method. The method receives the registration name of the resource as a parameter.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
const productService: ProductService = req.scope.resolve(
|
||||
"productService"
|
||||
)
|
||||
```
|
||||
|
||||
Please note that in API Routes some resources, such as repositories, aren't available. Refer to the [repositories](../entities/repositories.md#api-routes) documentation to learn how you can load them.
|
||||
|
||||
### In Subscribers
|
||||
|
||||
To resolve resources, such as services, in subscriber handler functions, use the `container` property of the handler function's parameter. The `container` has a method `resolve` which accepts the registration name of a resource as a parameter.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
import {
|
||||
ProductService,
|
||||
type SubscriberConfig,
|
||||
type SubscriberArgs,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export default async function productUpdateHandler({
|
||||
data, eventName, container, pluginOptions,
|
||||
}: SubscriberArgs<Record<string, string>>) {
|
||||
const productService: ProductService = container.resolve(
|
||||
"productService"
|
||||
)
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
// ...
|
||||
```
|
||||
|
||||
### In Classes
|
||||
|
||||
In classes such as services or strategies, you can load resources in the constructor function using dependency injection. The constructor receives an object of dependencies as a first parameter. Each dependency in the object should use the registration name of the resource that should be injected to the class.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
import { OrderService } from "@medusajs/medusa"
|
||||
|
||||
class OrderSubscriber {
|
||||
protected orderService: OrderService
|
||||
|
||||
constructor({ orderService }) {
|
||||
this.orderService = orderService
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Create services](../services/create-service.mdx)
|
||||
- [Create subscribers](../events/create-subscriber.md)
|
||||
@@ -1,204 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to perform local development in the Medusa monorepo. This includes how to use the dev CLI tool and perform unit, integration, and plugin tests.'
|
||||
---
|
||||
|
||||
# Local Development of Medusa Backend and Monorepo
|
||||
|
||||
In this document, you’ll learn how to customize Medusa’s core and run tests.
|
||||
|
||||
## Overview
|
||||
|
||||
As an open-source platform, Medusa’s core can be completely customized.
|
||||
|
||||
Whether you want to implement something differently, introduce a new feature as part of Medusa’s core or any of the other packages, or contribute to Medusa, this guide helps you learn how to run Medusa’s integration tests, as well as test your own Medusa core in a local backend.
|
||||
|
||||
### Medusa Repository Overview
|
||||
|
||||
[Medusa’s repository on GitHub](https://github.com/medusajs/medusa) includes all packages related to Medusa under the [`packages` directory](https://github.com/medusajs/medusa/tree/master/packages). This includes the [core Medusa package](https://github.com/medusajs/medusa/tree/master/packages/medusa), the [JS Client](https://github.com/medusajs/medusa/tree/master/packages/medusa-js), the CLI tools, and much more.
|
||||
|
||||
All the packages are part of a [Yarn workspace](https://classic.yarnpkg.com/lang/en/docs/workspaces/). So, when you run a command in the root of the project, such as `yarn build`, it goes through all registered packages in the workspace under the `packages` directory and runs the `build` command in each of those packages.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Yarn
|
||||
|
||||
When using and developing with the Medusa repository, it’s highly recommended that you use [Yarn](https://yarnpkg.com/getting-started/install) to avoid any errors or issues.
|
||||
|
||||
### Fork and Clone Medusa’s Repository
|
||||
|
||||
To customize Medusa’s core or contribute to it, you must first [fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo) and then [clone](https://docs.github.com/en/get-started/quickstart/fork-a-repo#cloning-your-forked-repository) the [GitHub repository](https://github.com/medusajs/medusa).
|
||||
|
||||
### Install Dependencies and Build Packages
|
||||
|
||||
In the directory of the forked GitHub repository, run the following commands to install necessary dependencies then build all packages in the repository:
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
yarn build
|
||||
```
|
||||
|
||||
### Medusa’s Dev CLI tool
|
||||
|
||||
Medusa provides a CLI tool to be used for development. This tool facilitates testing your local installment and changes to Medusa’s core without having to publish the changes to NPM.
|
||||
|
||||
:::note
|
||||
|
||||
The Medusa Dev CLI tool only works with yarn v1. You can use the following command to change the yarn version:
|
||||
|
||||
```bash
|
||||
yarn set version classic
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
To install Medusa’s dev CLI tool:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install medusa-dev-cli -g
|
||||
```
|
||||
|
||||
### Set the Location of the Medusa Repository
|
||||
|
||||
In the directory of your forked GitHub repository, run the following command to specify to the dev CLI tool the location of your Medusa repository:
|
||||
|
||||
```bash
|
||||
medusa-dev --set-path-to-repo `pwd`
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Run Tests in the Repository
|
||||
|
||||
In this section, you’ll learn how to run tests in the Medusa repository. This is helpful after you customize any of Medusa’s packages and want to make sure everything is still working as expected.
|
||||
|
||||
### Set System Environment Variables
|
||||
|
||||
Before you can run the tests, make sure you set the following system environment variables:
|
||||
|
||||
```bash
|
||||
DB_HOST=<YOUR_DB_HOST>
|
||||
DB_USERNAME=<YOUR_DB_USERNAME>
|
||||
DB_PASSWORD=<YOUR_PASSWORD>
|
||||
```
|
||||
|
||||
### Run Unit Tests
|
||||
|
||||
To run unit tests in all packages in the Medusa repository, run the following command in the root directory of the repository:
|
||||
|
||||
```bash
|
||||
yarn test
|
||||
```
|
||||
|
||||
This runs the `test` script defined in the `package.json` file of each package under the `packages` directory.
|
||||
|
||||
Alternatively, if you want to run the unit tests in a specific package, you can run the `test` command in the directory of that package.
|
||||
|
||||
For example, to run the unit tests of the Medusa core:
|
||||
|
||||
```bash
|
||||
cd packages/medusa
|
||||
yarn test
|
||||
```
|
||||
|
||||
### Run API Integration Tests
|
||||
|
||||
API integration tests are used to test out Medusa’s core API Routes.
|
||||
|
||||
To run the API integration tests, run the following command in the root directory of the repository:
|
||||
|
||||
```bash
|
||||
yarn test:integration:api
|
||||
```
|
||||
|
||||
### Run Plugin Integration Tests
|
||||
|
||||
Plugin integration tests are used to test out Medusa’s official plugins, which are also stored in the `packages` directory in the repository.
|
||||
|
||||
To run the plugin integration tests, run the following command in the root directory of the repository:
|
||||
|
||||
```bash
|
||||
yarn test:integration:plugins
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test in a Local Backend
|
||||
|
||||
Using Medusa’s dev CLI tool, you can test any changes you make to Medusa’s packages in a local backend installation. This eliminates the need to publish these packages on NPM publicly to be able to use them.
|
||||
|
||||
Medusa’s dev CLI tool scans and finds the Medusa packages used in your Medusa backend. Then, it copies the files of these packages from the `packages` directory in the Medusa repository into the `node_modules` directory of your Medusa backend.
|
||||
|
||||
:::info
|
||||
|
||||
Medusa’s Dev CLI tool uses the [path you specified earlier](#set-the-location-of-the-medusa-repository) to copy the files of the packages.
|
||||
|
||||
:::
|
||||
|
||||
### Copy Files to Local Backend
|
||||
|
||||
To test in a local backend:
|
||||
|
||||
1. Change to the directory of the backend you want to test your changes in:
|
||||
|
||||
```bash
|
||||
cd medusa-backend
|
||||
```
|
||||
|
||||
2\. Run the following command to copy the files from the `packages` directory of your Medusa repository into `node_modules`:
|
||||
|
||||
```bash
|
||||
medusa-dev
|
||||
```
|
||||
|
||||
By default, Medusa’s dev CLI runs in watch mode. So, it copies the files when you first run it. Then, whenever you make changes in the `dist` directory of the packages in the Medusa repository, it copies the changed files again.
|
||||
|
||||
### Watch and Compile Changes
|
||||
|
||||
While the above command is running, it's recommended to run the `watch` command inside the directory of every package you're making changes to.
|
||||
|
||||
The combination of these two commands running at the same time will compile the package into the `dist` directory of the package, then copy the compiled changes into your local backend.
|
||||
|
||||
For example, if you're making changes in the `medusa` package, run the following command inside the directory of the `medusa` package:
|
||||
|
||||
```bash title="packages/medusa"
|
||||
yarn watch
|
||||
```
|
||||
|
||||
Make sure the `medusa-dev` command is also running to copy the changes automatically.
|
||||
|
||||
Alternatively, you can manually run the `build` command every time you want to compile the changes:
|
||||
|
||||
```bash title="packages/medusa"
|
||||
yarn build
|
||||
```
|
||||
|
||||
### CLI Options
|
||||
|
||||
Here are some options you can use to customize how Medusa’s dev CLI tool works:
|
||||
|
||||
- `--scan-once` or `-s`: Copies files only one time then stops processing. If you make any changes after running the command with this option, you have to run the command again.
|
||||
|
||||
```bash
|
||||
medusa-dev -s
|
||||
```
|
||||
|
||||
- `--quiet` or `-q`: Disables showing any output.
|
||||
|
||||
```bash
|
||||
medusa-dev -q
|
||||
```
|
||||
|
||||
- `--packages`: Only copies specified packages. It accepts at least one package name. Package names are separated by a space.
|
||||
|
||||
```bash
|
||||
medusa-dev --packages @medusajs/medusa-cli medusa-file-minio
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Create a Plugin](../plugins/create.mdx)
|
||||
- [Contribution Guidelines](https://github.com/medusajs/medusa/blob/master/CONTRIBUTING.md)
|
||||
@@ -1,156 +0,0 @@
|
||||
---
|
||||
description: 'Learn about the Transaction Orchestrator used in the core Medusa package. The transaction orchestrator (TO) offers an effective way of managing transactions within an increasingly complex environment.'
|
||||
---
|
||||
|
||||
# Transaction Orchestrator
|
||||
|
||||
In this document, you’ll learn about the Transaction Orchestrator used in the core Medusa package.
|
||||
|
||||
## Introduction
|
||||
|
||||
The transaction orchestrator (TO) offers an effective way of managing transactions within an increasingly complex environment. It supports Medusa’s modularity and composability by handling transactions from different modules rather than one whole system.
|
||||
|
||||
Medusa’s core package uses the transaction orchestrator to enhance the control and management of transactions and workflows across multiple services or modules. It simplifies creating and executing distributed transactions.
|
||||
|
||||
The transaction orchestrator supervises transaction flows and guarantees that successful transactions are executed fully or entirely rolled back in case of failure. With clearly defined steps to Invoke and Compensate actions, the transaction orchestrator follows the separation of concerns principle, providing you with improved control over transactions and workflows.
|
||||
|
||||
---
|
||||
|
||||
## Why Medusa Uses the Transaction Orchestrator
|
||||
|
||||
The transaction orchestrator is a necessity for modular or distributed systems in scenarios where a given workflow involves different modules for several reasons:
|
||||
|
||||
1. **Data consistency:** In a distributed system, maintaining data consistency across different databases becomes challenging. A transaction orchestrator ensures that if any part of the transaction fails, all the previous steps are rolled back (compensated), keeping the data consistent across all involved databases.
|
||||
2. **Coordination:** The transaction orchestrator acts as a coordinator between different modules and their respective servers. It also manages the order of execution and communication between modules, ensuring that each transaction step is executed correctly and at the right time.
|
||||
3. **Simplifying complex workflows:** In a distributed environment, transactions can become complex due to the need to coordinate between different services and databases. A transaction orchestrator simplifies this process by abstracting away the complexities of managing distributed transactions, allowing developers to focus on implementing the business logic.
|
||||
4. **Scalability:** As a system grows, managing transactions across multiple services becomes increasingly difficult. A transaction orchestrator helps with scalability by providing a robust framework for managing distributed transactions, making it easier to maintain and expand the system.
|
||||
|
||||
In addition to the above reasons, there are reasons more relevant in the context of digital commerce which makes it an important addition to Medusa’s toolbox. These reasons are:
|
||||
|
||||
- **Composable architectures:** A transaction orchestrator supports composable architectures, allowing developers to easily combine and reuse modules as needed. This enables the creation of highly customizable commerce applications, tailored to specific business requirements.
|
||||
- **Adoption in legacy systems:** The transaction orchestrator can also facilitate the gradual transition of legacy systems to more modern, distributed architectures. This makes it easier for businesses to adopt and integrate new technologies without having to rebuild their entire infrastructure from scratch.
|
||||
- **Unlocking infrastructure technologies:** With a transaction orchestrator, developers can leverage advanced infrastructure technologies, such as serverless and edge computing. This can lead to improved performance, reduced latency, and increased reliability for commerce applications, resulting in better user experiences and higher customer satisfaction.
|
||||
|
||||
---
|
||||
|
||||
## Example of Using the Transaction Orchestrator
|
||||
|
||||
To better illustrate how the transaction orchestrator works, here’s an example of how it’s used in the multi-warehouse feature that coordinates the flow to create a product variant, creating an inventory item and finally linking both together:
|
||||
|
||||
```ts
|
||||
const createVariantFlow: TransactionStepsDefinition = {
|
||||
next: {
|
||||
action: "createVariantStep",
|
||||
saveResponse: true,
|
||||
next: {
|
||||
action: "createInventoryItemStep",
|
||||
saveResponse: true,
|
||||
next: {
|
||||
action: "attachInventoryItemStep",
|
||||
noCompensation: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The actions are handled by a single function called by the transaction orchestrator:
|
||||
|
||||
```ts
|
||||
async function transactionHandler(
|
||||
actionId: string,
|
||||
type: TransactionHandlerType,
|
||||
payload: TransactionPayload
|
||||
) {
|
||||
const command = {
|
||||
createVariantStep: {
|
||||
invoke: async (data: CreateProductVariantInput) => {
|
||||
return await createProductVariant(data) // omitted
|
||||
},
|
||||
compensate: async (
|
||||
data: CreateProductVariantInput,
|
||||
{ invoke }
|
||||
) => {
|
||||
await removeProductVariant(
|
||||
invoke.createVariantStep
|
||||
) // omitted
|
||||
},
|
||||
},
|
||||
createInventoryItemStep: {
|
||||
invoke: async (
|
||||
data: CreateProductVariantInput,
|
||||
{ invoke }
|
||||
) => {
|
||||
return await createInventoryItem(
|
||||
invoke.createVariantStep
|
||||
) // omitted
|
||||
},
|
||||
compensate: async (
|
||||
data: CreateProductVariantInput,
|
||||
{ invoke }
|
||||
) => {
|
||||
await removeInventoryItem(
|
||||
invoke.createInventoryItemStep
|
||||
) // omitted
|
||||
},
|
||||
},
|
||||
attachInventoryItemStep: {
|
||||
invoke: async (
|
||||
data: CreateProductVariantInput,
|
||||
{ invoke }
|
||||
) => {
|
||||
return await attachInventoryItem( // omitted
|
||||
invoke.createVariantStep,
|
||||
invoke.createInventoryItemStep
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return command[actionId][type](payload.data, payload.context)
|
||||
}
|
||||
```
|
||||
|
||||
Note that the implementation of each function was omitted to keep the example short; however, their names are self-explanatory.
|
||||
|
||||
Finally, the transaction orchestrator is instantiated and a new transaction initialized:
|
||||
|
||||
```ts
|
||||
const strategy = new TransactionOrchestrator(
|
||||
"create-variant-with-inventory", // transaction name
|
||||
createVariantFlow // transaction steps definition
|
||||
)
|
||||
|
||||
const transaction = await strategy.beginTransaction(
|
||||
ulid(), // unique id
|
||||
transactionHandler, // handler
|
||||
createProductVariantInput // input
|
||||
)
|
||||
await strategy.resume(transaction)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Achieving Cleaner Code with the Transaction Orchestrator
|
||||
|
||||
Utilizing a transaction orchestrator results in cleaner code and single-responsibility functions compared to manually managing distributed transactions. This is due to several factors:
|
||||
|
||||
1. **Abstraction:** A transaction orchestrator abstracts the complexity of managing distributed transactions by providing a standardized framework for defining transaction steps, their corresponding compensation actions, and the flow of execution.
|
||||
2. **Separation of concerns:** By clearly delineating the responsibilities of each function, the transaction orchestrator enforces the separation of concerns. Each function is responsible for either the "Invoke" (execution) or "Compensate" (rollback) action, ensuring they perform a single, specific task. This makes the code more readable, maintainable, and testable.
|
||||
3. **Modularity:** By using a transaction orchestrator, the code becomes more modular, as each step in the transaction is encapsulated within its own function. This allows developers to easily modify, add, or remove transaction steps without affecting the overall structure of the transaction.
|
||||
4. **Error Handling:** When handling distributed transactions manually, the code can become convoluted due to intricate error handling logic. The transaction orchestrator simplifies this by automatically managing errors and retries, allowing developers to create cleaner code without the need to address error handling for each step.
|
||||
5. **Reusability:** The transaction orchestrator allows you to define reusable functions for both the "Invoke" and "Compensate" actions, which can be easily reused across different transaction scenarios. This reduces code duplication and ensures that changes to a specific action only need to be made in one place.
|
||||
|
||||
---
|
||||
|
||||
## Handling Complex Workflows with the Transaction Orchestrator
|
||||
|
||||
The Transaction Orchestrator enables developers to create complex workflows for synchronous and long-running tasks that may take a while to receive a response (asynchronous). These workflows can be organized in a way that may not necessarily be transactional, but can still be effectively orchestrated.
|
||||
|
||||
There are several scenarios where asynchronous workflows with long-running steps can be applied using the transaction orchestrator:
|
||||
|
||||
1. **Order fulfillment:** In a commerce system, a workflow might involve creating an order, reserving inventory, charging the customer, generating shipping labels, and updating the shipping status. Some of these steps, such as generating shipping labels or charging the customer, could take a considerable amount of time due to external dependencies like payment gateways and shipping services.
|
||||
2. **Fraud detection and prevention:** fraud detection and prevention workflows might involve analyzing customer data, order patterns, and payment information to identify potential fraudulent activities. These workflows may include time-consuming tasks like querying external fraud detection APIs or applying machine learning models to analyze data.
|
||||
3. **Returns and refunds processing:** In a returns and refunds workflows, several steps may involve long-running tasks, such as receiving returned products, inspecting their condition, updating inventory, and processing refunds. Some of these steps may take a considerable amount of time, especially when dealing with external payment gateways or waiting for products to be returned and inspected.
|
||||
|
||||
In all these examples, workflows are valuable for handling intricate, multistep processes that include tasks taking considerable time or involving external dependencies without immediate responses.
|
||||
@@ -1,72 +0,0 @@
|
||||
---
|
||||
description: "Learn what an idempotency key is in Medusa. An Idempotency Key is a unique key associated with an operation that allows you to safely retry requests."
|
||||
---
|
||||
|
||||
import DocCard from '@theme/DocCard';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Idempotency Key
|
||||
|
||||
In this document, you'll learn what an idempotency key is in Medusa.
|
||||
|
||||
## Overview
|
||||
|
||||
An Idempotency Key is a unique, randomly generated key associated with an operation, such as the cart completion process. The idempotency key can be passed in the header of a request to an API Route. This allows you to safely retry requests without accidentally performing the same operation twice.
|
||||
|
||||
For example, if a connection error occurs while the customer is completing their cart and placing an order, you can retry from the last recovery point before the error occurred.
|
||||
|
||||
When an operation first starts, the idempotency key is generated using the `uuid` package's `v4` method. Then, the backend sets the following headers in the response:
|
||||
|
||||
```bash
|
||||
Access-Control-Expose-Headers: Idempotency-Key
|
||||
Idempotency-Key: <IDEM_VAL>
|
||||
```
|
||||
|
||||
Where `<IDEM_VAL>` is the idempotency key generated.
|
||||
|
||||
These headers can then be passed again for subsequent retrying requests and will be available on the response of these requests as well. The value of the idempotency key remains the same across requests and responses, even if an error occurs and you retry the request.
|
||||
|
||||
For example, when the cart completion process starts, the Medusa backend generates the idempotency key and sets the necessary headers on the response. If an error occurs in the request, you can later retry the request by passing these same headers in your request.
|
||||
|
||||
---
|
||||
|
||||
## IdempotencyKey Entity Overview
|
||||
|
||||
The idempotency key's data is stored within the `IdempotencyKey` entity. Some of its attributes include:
|
||||
|
||||
- `idempotency_key`: a unique string indicating the value of the idempotency key.
|
||||
- `request_method`: a string indicating the method of the latest request related to the idempotency key's operation. For example, `POST`.
|
||||
- `request_params`: a JSONB object indicating the parameters of the latest request related to the idempotency key's operation.
|
||||
- `request_path`: a string indicating the path of the latest request related to the idempotency key's operation.
|
||||
- `response_code`: a number indicating the response code of the latest request related to the idempotency key's operation.
|
||||
- `response_body`: a JSONB object indicating the response body of the latest request related to the idempotency key's operation.
|
||||
- `recovery_point`: a string indicating the point to continue from when retrying the request. The default value is `started`.
|
||||
|
||||
---
|
||||
|
||||
## Idempotency Key Stages
|
||||
|
||||
Idempotency key stages are the different recovery points that are available for an operation. Every operation has at least the `started` and `finished` stages.
|
||||
|
||||
For example, the cart completion operation has the following stages or recovery points:
|
||||
|
||||
- `started`
|
||||
- `tax_lines_created`
|
||||
- `payment_authorized`
|
||||
- `finished`
|
||||
|
||||
---
|
||||
|
||||
## Custom Development
|
||||
|
||||
You can use the `IdempotencyKeyService` in your custom development to ensure requests can be safely retried or continued.
|
||||
|
||||
<DocCard item={{
|
||||
type: 'link',
|
||||
href: '/development/idempotency-key/use-service',
|
||||
label: 'Use IdempotencyKeyService',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to use the IdempotencyKeyService in your code.'
|
||||
}
|
||||
}} />
|
||||
@@ -1,179 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to create a loader in Medusa. A loader can be created in the Medusa backend codebase, in a plugin, or in a module.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Use IdempotencyKeyService
|
||||
|
||||
In this document, you'll learn how to use the `IdempotencyKeyService`.
|
||||
|
||||
## Overview
|
||||
|
||||
You can use the `IdempotencyKeyService` within your custom development to ensure that your custom API Routes and operations can be safely retried or continued if an error occurs. This guide is also useful if you're overriding an existing feature in Medusa that uses the `IdempotencyKeyService` and you want to maintain its usage, such as if you're overriding the cart completion strategy.
|
||||
|
||||
The `IdempotencyKeyService` includes methods that can be used to create and update idempotency keys, among other functionalities.
|
||||
|
||||
---
|
||||
|
||||
## Create Idempotency Key
|
||||
|
||||
You can create an idempotency key within an API Route using the `create` method of the `IdempotencyKeyService`:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
import { IdempotencyKeyService } from "@medusajs/medusa"
|
||||
|
||||
export const POST = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
// ...
|
||||
const idempotencyKeyService = req.scope.resolve<
|
||||
IdempotencyKeyService
|
||||
>("idempotencyKeyService")
|
||||
const idempotencyKey = await idempotencyKeyService.create({
|
||||
request_method: req.method,
|
||||
request_params: req.params,
|
||||
request_path: req.path,
|
||||
})
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The method requires as a parameter an object having the following properties:
|
||||
|
||||
- `request_method`: a string indicating the request method to be associated with the idempotency key.
|
||||
- `request_params`: an object indicating the request parameters to be associated with the idempotency key.
|
||||
- `request_path`: a string indicating the request path to be associated with the idempotency key.
|
||||
|
||||
The method handles generating the idempotency key value and saving the idempotency key with its details in the database. It returns the full idempotency key object.
|
||||
|
||||
Alternatively, you can use the `initializeRequest` method that allows you to retrieve an idempotency key based on the value passed in the `Idempotency-Key` header of the request if it exists, or create a new key otherwise. For example:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
import {
|
||||
IdempotencyKeyService,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export const POST = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
// ...
|
||||
const idempotencyKeyService = req.scope.resolve<
|
||||
IdempotencyKeyService
|
||||
>("idempotencyKeyService")
|
||||
const headerKey = req.get("Idempotency-Key") || ""
|
||||
|
||||
const idempotencyKey = await idempotencyKeyService
|
||||
.initializeRequest(
|
||||
headerKey,
|
||||
req.method,
|
||||
req.params,
|
||||
req.path
|
||||
)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The method requires the following parameters:
|
||||
|
||||
1. The first parameter is the key existing in the header of the request, if there's any.
|
||||
2. The second parameter is the request's method.
|
||||
3. The third parameter is the request's parameters.
|
||||
4. The fourth method is the request's path.
|
||||
|
||||
The method returns the full idempotency key object.
|
||||
|
||||
---
|
||||
|
||||
## Perform Actions Within Idempotency Key Stages
|
||||
|
||||
Each [idempotency key stage](./overview.mdx#idempotency-key-stages) typically has transactions performed within it. Using the `IdempotencyKeyService`'s `workStage` method allows you to perform related functionalities in transactional isolation within each stage. You can access the stage or recovery point of an idempotency key using the `recovery_point` attribute.
|
||||
|
||||
The following example is taken from the `CartCompletionStrategy` implemented in the Medusa backend:
|
||||
|
||||
<!-- eslint-disable no-fallthrough -->
|
||||
|
||||
```ts
|
||||
class CartCompletionStrategy
|
||||
extends AbstractCartCompletionStrategy {
|
||||
// ...
|
||||
|
||||
async complete(
|
||||
id: string,
|
||||
ikey: IdempotencyKey,
|
||||
context: RequestContext
|
||||
): Promise<CartCompletionResponse> {
|
||||
// ...
|
||||
let inProgress = true
|
||||
let err: unknown = false
|
||||
|
||||
while (inProgress) {
|
||||
switch (idempotencyKey.recovery_point) {
|
||||
case "started": {
|
||||
await this.activeManager_
|
||||
.transaction(
|
||||
"SERIALIZABLE",
|
||||
async (transactionManager) => {
|
||||
idempotencyKey =
|
||||
await this.idempotencyKeyService_
|
||||
.withTransaction(transactionManager)
|
||||
.workStage(
|
||||
idempotencyKey.idempotency_key,
|
||||
async (manager) =>
|
||||
await this.handleCreateTaxLines(
|
||||
id,
|
||||
{
|
||||
manager,
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
.catch((e) => {
|
||||
inProgress = false
|
||||
err = e
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
case "tax_lines_created": {
|
||||
// ...
|
||||
}
|
||||
|
||||
case "payment_authorized": {
|
||||
// ...
|
||||
}
|
||||
|
||||
case "finished": {
|
||||
// ...
|
||||
}
|
||||
|
||||
default: {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The method requires the following parameters:
|
||||
|
||||
1. The first parameter is the idempotency key value.
|
||||
2. The second parameter is a callback function to be executed. The function should return an object that is used to update the idempotency key's details. The object can include the following parameters:
|
||||
1. `recovery_point`: a string indicating the new recovery point associated with the idempotency key's operation. If no `recovery_point` is returned in the object, the `finished` recovery point is assigned by default.
|
||||
2. `response_code`: a number indicating the latest response code of the idempotency key's operation.
|
||||
3. `response_body`: an object indicating the latest response body of the idempotency key's operation.
|
||||
|
||||
The method returns an updated idempotency key object.
|
||||
@@ -1,89 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to create a loader in Medusa. A loader can be created in the Medusa backend codebase, in a plugin, or in a module.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Create a Loader
|
||||
|
||||
In this document, you’ll learn how to create a loader in Medusa. A loader can be created in the Medusa backend codebase, in a plugin, or in a module.
|
||||
|
||||
## Step 1: Create Loader File
|
||||
|
||||
Create a TypeScript or JavaScript file in the `src/loaders` directory that will hold your custom script. There are no restrictions on the name of the file.
|
||||
|
||||
For example, create the file `src/loaders/my-loader.ts` that will hold the loader.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Define the Loader
|
||||
|
||||
The loader file must export a function.
|
||||
|
||||
### Parameters of Loaders in Medusa Backend and Plugins
|
||||
|
||||
When the loader is defined in the Medusa backend or a plugin, the function receives the following parameters:
|
||||
|
||||
1. `container`: the first parameter, which is a `MedusaContainer` object. You can use the container to register custom resources into the dependency container or resolve resources from the dependency container.
|
||||
2. `config`: the second parameter, which is an object that holds the loader’s plugin options. If the loader is not created inside a plugin, the config object will be empty.
|
||||
|
||||
### Parameters of Loaders in Modules
|
||||
|
||||
When the loader is defined in a module, it receives the following parameters:
|
||||
|
||||
1. `container`: the first parameter, which is a `MedusaContainer` object. You can use the container to register custom resources into the dependency container or resolve resources from the dependency container.
|
||||
2. `logger`: the second parameter, which is a `Logger` object. The logger can be used to log messages in the console.
|
||||
3. `config`: the third parameter, which is an object that holds the loader’s module options.
|
||||
|
||||
### Example Implementation
|
||||
|
||||
For example, this loader function resolves the `ProductService` and logs in the console the count of products in the Medusa backend:
|
||||
|
||||
```ts title="src/loaders/my-loader.ts"
|
||||
import {
|
||||
ProductService,
|
||||
ConfigModule,
|
||||
MedusaContainer,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export default async (
|
||||
container: MedusaContainer,
|
||||
config: ConfigModule
|
||||
): Promise<void> => {
|
||||
console.info("Starting loader...")
|
||||
const productService = container.resolve<ProductService>(
|
||||
"productService"
|
||||
)
|
||||
console.info(`Products count: ${
|
||||
await productService.count()
|
||||
}`)
|
||||
console.info("Ending loader...")
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Run Build Command
|
||||
|
||||
In the directory of your project, run the following command to transpile the files from the `src` to the `dist` directory:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test it Out
|
||||
|
||||
:::note
|
||||
|
||||
This section explains how to test out the loader if it’s created in the Medusa backend codebase. If you’re creating your loader in a plugin, you can learn how to test it in the [plugins documentation](../plugins/create.mdx#test-your-plugin). Alternatively, if you’re creating your loader in a module, you can learn how to test it in the [modules documentation](../modules/create.mdx#step-4-test-your-module).
|
||||
|
||||
:::
|
||||
|
||||
Run the following command to start the Medusa backend:
|
||||
|
||||
```bash npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
Your loader script should run on the Medusa backend startup. If you logged a message in the console, similar to the example above, you should see it in the console.
|
||||
@@ -1,42 +0,0 @@
|
||||
---
|
||||
description: "Learn what loaders are in Medusa. A loader is a script that runs when the Medusa backend starts."
|
||||
---
|
||||
|
||||
import DocCard from '@theme/DocCard';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Loaders
|
||||
|
||||
In this document, you’ll learn what loaders are in Medusa.
|
||||
|
||||
## Overview
|
||||
|
||||
A loader is a script that runs when the Medusa backend starts. The Medusa backend uses loaders to initialize the database connection, load plugins, register resources in the dependency container, and more.
|
||||
|
||||
Loaders can be created within the Medusa backend codebase, in a plugin, or in a module to perform custom actions when the backend starts. The loader is created in a TypeScript or JavaScript file located in the `src/loaders` directory, then transpiled using the `build` command into the `dist/loaders` directory.
|
||||
|
||||
### Loader Examples
|
||||
|
||||
For example, the Redis Event Bus module uses a loader to establish a connection with Redis and log a message in the console.
|
||||
|
||||
Another example is the Algolia plugin, which uses a loader to update the index settings when the Medusa backend starts based on the plugin’s options.
|
||||
|
||||
Loaders can be used to access the dependency container and register custom resources in it.
|
||||
|
||||
Loaders can also be used to create [scheduled jobs](../scheduled-jobs/overview.mdx) that run at a specified interval of time.
|
||||
|
||||
---
|
||||
|
||||
## Custom Development
|
||||
|
||||
Developers can create a loader with the desired functionality directly within the Medusa Core, in a plugin, or in a module.
|
||||
|
||||
<DocCard item={{
|
||||
type: 'link',
|
||||
href: '/development/loaders/create',
|
||||
label: 'Create a Loader',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a loader.'
|
||||
}
|
||||
}} />
|
||||
@@ -1,172 +0,0 @@
|
||||
# Logging
|
||||
|
||||
In this document, you’ll learn about how you can log messages in the Medusa backend and available logging configurations.
|
||||
|
||||
## Overview
|
||||
|
||||
In the Medusa backend, the `Logger` class is registered in the [dependency container](../fundamentals/dependency-injection.md). This class provides helper methods to log messages in the console while the Medusa backend is running.
|
||||
|
||||
You can resolve this class in your resources, such as services or loaders, to show a message in the console. You can also configure how and where logs are shown in your backend using environment variables.
|
||||
|
||||
---
|
||||
|
||||
## How to Log a Message
|
||||
|
||||
To log a message, resolve the `logger` registration name using dependency injection in your resource.
|
||||
|
||||
For example, to log a message in a [loader](../loaders/overview.mdx):
|
||||
|
||||
```ts title="src/loaders/my-loader.ts"
|
||||
import {
|
||||
ProductService,
|
||||
ConfigModule,
|
||||
Logger,
|
||||
MedusaContainer,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export default async (
|
||||
container: MedusaContainer,
|
||||
config: ConfigModule
|
||||
): Promise<void> => {
|
||||
const logger = container.resolve<Logger>("logger")
|
||||
|
||||
logger.info("Starting loader...")
|
||||
|
||||
const productService = container.resolve<ProductService>(
|
||||
"productService"
|
||||
)
|
||||
|
||||
logger.info(`Products count: ${
|
||||
await productService.count()
|
||||
}`)
|
||||
|
||||
logger.info("Ending loader")
|
||||
}
|
||||
```
|
||||
|
||||
In the example above, you resolve the `logger` from the dependency container and then use its `info` method to show an info message in the console.
|
||||
|
||||
If you start your backend, you should see the messages in the loader logged in the console.
|
||||
|
||||
:::note
|
||||
|
||||
Learn how to resolve dependencies within other types of resources in the [dependency container guide](../fundamentals/dependency-injection.md#resolve-resources).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Log Levels
|
||||
|
||||
The following `Logger` methods accept a string parameter that should be logged in the console with a specific level:
|
||||
|
||||
- `info`: The message is logged with level `info`.
|
||||
- `warn`: The message is logged with level `warn`.
|
||||
- `error`: The message is logged with level `error`.
|
||||
- `debug`: The message is logged with level `debug`.
|
||||
|
||||
---
|
||||
|
||||
## Show Log with Progress
|
||||
|
||||
The `activity` method is used to log a message of level `info`. If the Medusa backend is running in a development environment, a spinner starts that can be used to show progress and succeed or fail the progress.
|
||||
|
||||
The `activity` method returns the ID of the started activity. This ID can then be passed to one of the following methods:
|
||||
|
||||
- `progress`: Log a message of level `info` that indicates progress within that same activity.
|
||||
- `failure`: Log a message of level `error` that indicates that the activity has failed. This also ends the associated activity.
|
||||
- `success`: Log a message of level `info` that indicates that activity has succeeded. This also ends the associated activity.
|
||||
|
||||
:::note
|
||||
|
||||
If you configured the `LOG_LEVEL` environment variable to a level higher than those associated with the above methods, their messages won’t be logged.
|
||||
|
||||
:::
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/loaders/my-loader.ts"
|
||||
import {
|
||||
ProductService,
|
||||
ConfigModule,
|
||||
Logger,
|
||||
MedusaContainer,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export default async (
|
||||
container: MedusaContainer,
|
||||
config: ConfigModule
|
||||
): Promise<void> => {
|
||||
const logger = container.resolve<Logger>("logger")
|
||||
|
||||
const activityId = logger.activity("Starting loader...")
|
||||
|
||||
const productService = container.resolve<ProductService>(
|
||||
"productService"
|
||||
)
|
||||
|
||||
try {
|
||||
logger.progress(activityId, `Products count: ${
|
||||
await productService.count()
|
||||
}`)
|
||||
} catch (e) {
|
||||
logger.failure(activityId, `An error occurred: ${e}`)
|
||||
}
|
||||
|
||||
logger.success(activityId, "Ending loader")
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Logging Configuration
|
||||
|
||||
### Log Level
|
||||
|
||||
By default, the minimum logged level is `silly`, meaning that messages of all levels are logged.
|
||||
|
||||
You can change that by setting the `LOG_LEVEL` environment variable to the minimum level you want to be logged.
|
||||
|
||||
The available log levels, from lowest to highest levels, are:
|
||||
|
||||
1. `silly`: this is used to indicate that messages of all levels should be logged.
|
||||
2. `debug`
|
||||
3. `info`
|
||||
4. `warn`
|
||||
5. `error`
|
||||
|
||||
For example, to log only `error` messages set the `LOG_LEVEL` environment variable to `error`:
|
||||
|
||||
```bash
|
||||
LOG_LEVEL=error
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
The environment variable must be set as a system environment variable and not in `.env`.
|
||||
|
||||
:::
|
||||
|
||||
### Save Logs in File
|
||||
|
||||
Aside from showing the logs in the Medusa backend’s console, you can save the backend’s logs in a file by setting the `LOG_FILE` environment variable to the path of the file relative to the Medusa backend’s root directory.
|
||||
|
||||
:::note
|
||||
|
||||
This doesn’t save logs of requests sent to the Medusa backend.
|
||||
|
||||
:::
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
LOG_FILE=all.log
|
||||
```
|
||||
|
||||
Your logs are now saved in the `all.log` file at the root of your Medusa backend.
|
||||
|
||||
:::note
|
||||
|
||||
The environment variable must be set as a system environment variable and not in `.env`.
|
||||
|
||||
:::
|
||||
@@ -1,309 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to create a module and test the module in your Medusa backend.'
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# How to Create a Module
|
||||
|
||||
In this document, you’ll learn how to create a module and test the module in your Medusa backend.
|
||||
|
||||
This document covers the general steps of creating an internal module, but does not explore actually implementing any functionality in it. An internal module is a TypeScript/JavaScript module that is loaded by the Medusa backend as part of the commerce application to extend or replace a functionality within it, such as the cache or event bus functionality.
|
||||
|
||||
---
|
||||
|
||||
## (Optional) Step 0: Project Preparation
|
||||
|
||||
Before you start implementing the custom functionality in your module, it's recommended to create a directory that holds your module and prepare the following structure in it:
|
||||
|
||||
```
|
||||
custom-module
|
||||
|
|
||||
|___ src
|
||||
|
|
||||
|___ index.ts
|
||||
|
|
||||
|___ services // directory
|
||||
```
|
||||
|
||||
The directory can be an NPM project, but that is optional. `index.ts` acts as an entry point to your Module. You'll learn about its content in a later step. The `service` directory will hold your custom services. If you're adding other resources you can add other directories for them. For example, if you're adding an entity you can add a `models` directory.
|
||||
|
||||
:::tip
|
||||
|
||||
You can use JavaScript instead of TypeScript.
|
||||
|
||||
:::
|
||||
|
||||
It's also recommended to use the following TypeScript config in `tsconfig.json`, which should be added in the root of the project holding your module:
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"es2020"
|
||||
],
|
||||
"target": "2020",
|
||||
"outDir": "./dist",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"sourceMap": true,
|
||||
"noImplicitReturns": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"noImplicitThis": true,
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": [
|
||||
"dist",
|
||||
"./src/**/__tests__",
|
||||
"./src/**/__mocks__",
|
||||
"./src/**/__fixtures__",
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Implement the Custom Functionality
|
||||
|
||||
This step depends on what you’re actually implementing. For example, you can implement the cache or events module, or you can implement a commerce module. If what you’re creating has a guide, you can refer to it while implementing the functionalities.
|
||||
|
||||
### Note About Project Structure
|
||||
|
||||
When developing your module, it's important to note that you'll later be referencing the module using a file path to an `index.ts` or `index.js` file. This file is explained in the next step and acts as an entry point and a definition of your module.
|
||||
|
||||
So, make sure when implementing your module you take into account that the module should be easily referenced from your local Medusa server. For example, you can develop your module in a sibling directory of the Medusa backend that you can reference with `../custom-module`.
|
||||
|
||||
Keep in mind that when you publish the module to NPM, you'll have to move your module into a new NPM project. This is covered in the [Publish Module documentation](./publish.md).
|
||||
|
||||
### Recommended Guides
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/cache/create',
|
||||
label: 'Create a Cache Module',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a cache module in Medusa.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/events/create-module',
|
||||
label: 'Create an Events Module',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create an events module in Medusa.',
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
### Step 2: Export Module
|
||||
|
||||
After implementing the module, you must export a module object that helps the Medusa backend understand how to use this Module. This is done in the file `index.ts`.
|
||||
|
||||
The file must export an object with the following properties:
|
||||
|
||||
```ts
|
||||
type ModuleExports = {
|
||||
service: Constructor<any>
|
||||
loaders?: ModuleLoaderFunction[]
|
||||
migrations?: any[]
|
||||
models?: Constructor<any>[]
|
||||
runMigrations?(
|
||||
options: LoaderOptions,
|
||||
moduleDeclaration: InternalModuleDeclaration
|
||||
): Promise<void>
|
||||
revertMigration?(
|
||||
options: LoaderOptions,
|
||||
moduleDeclaration: InternalModuleDeclaration
|
||||
): Promise<void>
|
||||
}
|
||||
```
|
||||
|
||||
:::tip
|
||||
|
||||
All property types such as `ModuleLoaderFunction` can be loaded from the `@medusajs/modules-sdk` package.
|
||||
|
||||
:::
|
||||
|
||||
Where:
|
||||
|
||||
- `service`: This is the only required property to be exported. It should be the main service your module exposes, and it must implement all the declared methods on the module interface. For example, if it's a cache module, it must implement the `ICacheService` interface exported from `@medusajs/types`.
|
||||
- `loaders`: (optional) an array of [loader](../loaders/overview.mdx) functions used to perform an action while loading the module. For example, you can log a message that the module has been loaded, or if your module's scope is [isolated](#module-scope) you can use the loader to establish a database connection.
|
||||
- `migrations`: (optional) an array of objects containing database migrations that should run when the `migration` command is used with Medusa's CLI.
|
||||
- `models`: (optional) an array of entities that your module creates.
|
||||
- `runMigrations`: (optional) a function that can be used to define migrations to run when the `migration run` command is used with Medusa's CLI. The migrations will only run if they haven't already. This will only be executed if the module's scope is [isolated](#module-scope).
|
||||
- `revertMigration`: (optional) a function can be used to define how migrations should be reverted when the `migration revert` command is used with Medusa's CLI. This will only be executed if the module's scope is [isolated](#module-scope).
|
||||
|
||||
Here's an example implementation of `index.ts` from Medusa's Redis Cache module:
|
||||
|
||||
```ts title="index.ts"
|
||||
import { ModuleExports } from "@medusajs/modules-sdk"
|
||||
|
||||
import Loader from "./loaders"
|
||||
import { RedisCacheService } from "./services"
|
||||
|
||||
const service = RedisCacheService
|
||||
const loaders = [Loader]
|
||||
|
||||
const moduleDefinition: ModuleExports = {
|
||||
service,
|
||||
loaders,
|
||||
}
|
||||
|
||||
export default moduleDefinition
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Reference Module
|
||||
|
||||
To use your module in the Medusa backend, add your module to `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
// ...
|
||||
modules: {
|
||||
// ...
|
||||
moduleType: {
|
||||
resolve: "module-name-or-path",
|
||||
options: {
|
||||
// options if necessary
|
||||
},
|
||||
// optional
|
||||
resources: "shared",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The way you add your module depends on its type and what options it requires, if any. Note that in the above code example:
|
||||
|
||||
- `moduleType` is the type of your module. For example, if your module is a cache module, it should be changed to `cacheService`.
|
||||
- `resolve` is used to reference the Module. Its value should be either the name of an NPM package module, or a relative file path to the module. This is explained more in the [Module Reference section](#module-reference).
|
||||
- `options` should hold any options of your module, if necessary.
|
||||
- `resources` is an optional property that indicates whether the module shares the same dependency container as the rest of the resources in the Medusa backend. More details are explained in the [Module Scope section](#module-scope).
|
||||
|
||||
### Module Reference
|
||||
|
||||
When the module is installed as an NPM package, the value of the `resolve` property should be the name of that package. For example:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
// ...
|
||||
modules: {
|
||||
// ...
|
||||
moduleType: {
|
||||
resolve: "custom-module",
|
||||
// ...
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
However, when using a local module, you must reference the module using a relative file path to it from the Medusa backend.
|
||||
|
||||
For example, consider you have the following file structure:
|
||||
|
||||
```
|
||||
|
|
||||
|___ custom-module
|
||||
| |
|
||||
| |___ src
|
||||
| |
|
||||
| |___ index.ts
|
||||
| |
|
||||
| |___ services
|
||||
| | |
|
||||
| | |___ custom-service.ts
|
||||
| |___ // more files
|
||||
|
|
||||
|
|
||||
|___ medusa-backend
|
||||
```
|
||||
|
||||
You can reference your module in two ways:
|
||||
|
||||
1\. Referencing the directory: In this case, it's assumed that the `index.ts` file that contains the module definition is in the root of the directory you referenced. Using the above example, the file path would be in this case:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
// ...
|
||||
modules: {
|
||||
// ...
|
||||
moduleType: {
|
||||
resolve: "../custom-module/src",
|
||||
// ...
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
2\. Referencing `index` file: In this case, it's assumed that the `index.ts` or `index.js` file you're referencing includes the module definition. Using the above example, the file path would be in this case:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
// ...
|
||||
modules: {
|
||||
// ...
|
||||
moduleType: {
|
||||
resolve: "../custom-module/src/index.ts",
|
||||
// ...
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Module Scope
|
||||
|
||||
By default, the module shares the same dependency container used across the Medusa backend. So, the module can benefit from the core services and other resources available through [dependency injection](../fundamentals/dependency-injection.md). The module can also benefit from the same database connection.
|
||||
|
||||
The module's scope can be changed using the `resources` property available as part of the module's configurations:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
// ...
|
||||
modules: {
|
||||
// ...
|
||||
moduleType: {
|
||||
// other configurations
|
||||
resources: "shared",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The `resources` property can have one of the following values:
|
||||
|
||||
- `shared`: (default) The dependency container is shared with the module, including the database connection. You don't need to establish the database connection yourself in a loader.
|
||||
- `isolated`: the module receives an empty dependency container, and only its own dependencies will be registered in the container. When using this value, you must establish the database connection yourself and managing other resources within your module.
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Test Your Module
|
||||
|
||||
Finally, to test your module, run the following command:
|
||||
|
||||
```bash npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
This starts the Medusa backend and runs your module as part of it.
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
After you finish developing your module, you can publish it as an NPM package with [this guide](./publish.md).
|
||||
@@ -1,47 +0,0 @@
|
||||
---
|
||||
description: 'Learn what Modules are and how can you use them during your custom development with Medusa.'
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Modules
|
||||
|
||||
In this document, you’ll learn what Modules are and how can you use them during your custom development with Medusa.
|
||||
|
||||
## Overview
|
||||
|
||||
Modules are self-contained, reusable pieces of code that encapsulate specific functionality or features within an ecommerce application. They foster separation of concerns, maintainability, and reusability by organizing code into smaller, independent units that can be easily managed, tested, and integrated with other modules.
|
||||
|
||||
Modules further increase Medusa’s extensibility. commerce modules, such as the cart engine, can be extended or entirely replaced with your own custom logic. They can also run independently of the core Medusa package, allowing you to utilize the commerce module within a larger commerce ecosystem. For example, you can use the Order Module as an Order Management System (OMS) without using Medusa’s core.
|
||||
|
||||
This also applies to core logic such as caching or events systems. You can use modules to integrate any logic or third-party service to handle this logic. This gives you greater flexibility in how you choose your tech stack.
|
||||
|
||||
Modules are created and loaded similarly to plugins. They can be loaded from a local project, or they can be installed and loaded from an NPM package. In the Medusa backend, they’re added as part of the configurations in `medusa-config.js` to use and load them within the backend.
|
||||
|
||||
---
|
||||
|
||||
## Custom Development
|
||||
|
||||
Developers can create their own modules and use them in their Medusa backend. They can also publish these modules to NPM to reuse them across Medusa backend or allow other developers to use them.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/modules/create',
|
||||
label: 'Create a Module',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a module in Medusa.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/modules/publish',
|
||||
label: 'Publish Module',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to publish your module on NPM.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
@@ -1,198 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to prepare and publish your custom module to NPM, then how to install it in the Medusa backend.'
|
||||
---
|
||||
|
||||
# How to Publish a Module
|
||||
|
||||
In this document, you'll learn how to prepare and publish your custom module to NPM, then how to install it in the Medusa backend.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
This guide assumes you've already created a custom module. If not, follow [this guide](./create.mdx) first to create a module.
|
||||
|
||||
You also need an [NPM account](https://www.npmjs.com/signup) to publish the module with.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create an NPM Project
|
||||
|
||||
If your module isn't located in an NPM project already, you must create one first that will hold your module.
|
||||
|
||||
To do that, run the following commands to create a directory and initialize an NPM project in it:
|
||||
|
||||
```bash npm2yarn
|
||||
mkdir my-module
|
||||
npm init
|
||||
```
|
||||
|
||||
You’ll be asked a couple of questions related to your package, such as its name or license. You can keep the default for now or set them right away.
|
||||
|
||||
Once you’re done, you should have a `package.json` created in the directory.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Changes to package.json
|
||||
|
||||
In your `package.json` file, add or update the following fields:
|
||||
|
||||
```json title="package.json"
|
||||
{
|
||||
// other fields
|
||||
"main": "dist/index.js",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@medusajs/types": "^0.0.2",
|
||||
"cross-env": "^5.2.1",
|
||||
"typescript": "^4.4.4"
|
||||
},
|
||||
"scripts": {
|
||||
"watch": "tsc --build --watch",
|
||||
"prepare": "cross-env NODE_ENV=production npm run build",
|
||||
"build": "tsc --build",
|
||||
},
|
||||
"dependencies": {
|
||||
"@medusajs/modules-sdk": "^0.1.0",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This adds the necessary dependencies for development and publishing, including the `@medusajs/modules-sdk` package. It also adds the following scripts:
|
||||
|
||||
- `build`: can be used to manually build your module.
|
||||
- `prepare`: can be used to prepare your module for publishing on NPM
|
||||
- `watch`: (optional, for development) can be used to re-build your module whenever any changes occur without having to manually trigger the `build`.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Configure tsconfig.json
|
||||
|
||||
If you don't already have a `tsconfig.json` file, create one in the root of your NPM project with the following content:
|
||||
|
||||
```json title="tsconfig.json"
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"es2020"
|
||||
],
|
||||
"target": "2020",
|
||||
"outDir": "./dist",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"sourceMap": true,
|
||||
"noImplicitReturns": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"noImplicitThis": true,
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": [
|
||||
"dist",
|
||||
"./src/**/__tests__",
|
||||
"./src/**/__mocks__",
|
||||
"./src/**/__fixtures__",
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
This allows you to use the recommended TypeScript configurations and sets the output directory to `dist`. This is essential for preparing your module for publishing.
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Change Module Structure
|
||||
|
||||
To ensure that the files are built from the `src` directory to the `dist` directory, make sure to move the module content to a `src` directory inside the new NPM project.
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Publish and Use Module
|
||||
|
||||
This section explains how to publish your module to NPM.
|
||||
|
||||
### Run Prepare Command
|
||||
|
||||
Before you publish or update your module, make sure to run the `prepare` command defined earlier:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run prepare
|
||||
```
|
||||
|
||||
### Login
|
||||
|
||||
In your terminal, log in with your NPM account:
|
||||
|
||||
```bash
|
||||
npm login
|
||||
```
|
||||
|
||||
You’ll be asked to enter your NPM email and password.
|
||||
|
||||
### Publish Module Package
|
||||
|
||||
Once you’re logged in, you can publish your package with the following command:
|
||||
|
||||
```bash
|
||||
npm publish
|
||||
```
|
||||
|
||||
Your package is then published on NPM and everyone can use it and install it.
|
||||
|
||||
### Install Module
|
||||
|
||||
To install your published module, you can run the following command on any Medusa backend project:
|
||||
|
||||
```bash
|
||||
npm install module-name
|
||||
```
|
||||
|
||||
Where `module-name` is the name of your module.
|
||||
|
||||
### Add Module to medusa-config.js
|
||||
|
||||
In `medusa-config.js` on your Medusa backend, add your module to the exported configurations:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
// ...
|
||||
modules: {
|
||||
// ...
|
||||
moduleType: {
|
||||
resolve: "<module-name>",
|
||||
options: {
|
||||
// options if necessary
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Where `<module-name>` is the name of your NPM package.
|
||||
|
||||
You can learn more about the available options in the [Create Module documentation](./create.mdx#step-3-reference-module).
|
||||
|
||||
### Update Module
|
||||
|
||||
To update your module at a later point, you can run the following command to change the NPM version:
|
||||
|
||||
```bash
|
||||
npm version <type>
|
||||
```
|
||||
|
||||
Where `<type>` indicates the type of version update you’re publishing. For example, it can be `major` or `minor`. You can see the [full list of types in NPM’s documentation](https://docs.npmjs.com/cli/v8/commands/npm-version).
|
||||
|
||||
Then, publish the new update:
|
||||
|
||||
```bash
|
||||
npm publish
|
||||
```
|
||||
@@ -1,109 +0,0 @@
|
||||
---
|
||||
description: 'Learn about the Notification architecture in Medusa and the automation flow. The Notification Architecture is made up of the Notification Provider and Notification.'
|
||||
---
|
||||
|
||||
import DocCard from '@theme/DocCard';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Notification Architecture Overview
|
||||
|
||||
This document gives an overview of the notification architecture and how it works.
|
||||
|
||||
## Introduction
|
||||
|
||||
Medusa provides a Notification API to mainly handle sending and resending notifications when an event occurs. For example, sending an email to the customer when they place an order.
|
||||
|
||||
The Notification architecture is made up of two main components: the Notification Provider and the Notification. Simply put, the Notification Provider handles the sending and resending of a Notification.
|
||||
|
||||
---
|
||||
|
||||
## Notification Provider
|
||||
|
||||
A Notification Provider is a provider that handles sending and resending of notifications. You can either create and integrate your own provider or install a Notification Provider through a third-party plugin.
|
||||
|
||||
An example of a notification provider is SendGrid. When an order is placed, the SendGrid plugin sends an email to the customer.
|
||||
|
||||
### How Notification Provider is Created
|
||||
|
||||
A Notification Provider is essentially a Medusa [Service](../services/create-service.mdx) with a unique identifier, and it extends the [`NotificationService`](../../references/services/classes/services.NotificationService.mdx) provided by the `medusa-interfaces` package. It can be created as part of a [Plugin](../plugins/overview.mdx), or it can be created just as a Service file in your Medusa backend.
|
||||
|
||||
As a developer, you mainly work with the Notification Provider when integrating a third-party service that handles notifications in Medusa.
|
||||
|
||||
When you run your Medusa backend, the Notification Provider is registered in your backend. If it's a new Notification Provider, it will be inserted into the `notification_provider` table in your database.
|
||||
|
||||
### NotificationProvider Entity Overview
|
||||
|
||||
The `NotificationProvider` entity only has 2 attributes: `id` and `is_installed`.
|
||||
|
||||
`id` is the value of the static property `identifier` defined inside the notification Service class.
|
||||
|
||||
`is_installed` indicates whether the Notification Provider is installed or not. When you install a Notification Provider, the value of this attribute is `true`.
|
||||
|
||||
If you installed a Notification provider and then removed the Service files or plugin that registered the Notification Provider, the Notification Provider remains in your database, but the value of the `is_installed` field changes to `false`.
|
||||
|
||||
---
|
||||
|
||||
## Notification
|
||||
A notification is a form of an alert sent to the customers or users to inform them of an action that has occurred. For example, if an order is placed, the notification, in this case, can be an email that confirms their order and lists the order details.
|
||||
|
||||
Notifications can take on other forms such as an SMS or a Slack message.
|
||||
|
||||
### How Notification is Created
|
||||
|
||||
Notifications are created in the `NotificationService` class in Medusa’s core after the Notification has been handled by the Notification Provider.
|
||||
|
||||
The data and additional details that the Notification Provider returns to the `NotificationService` is used to fill some of the attributes of the Notification in the database.
|
||||
|
||||
A Notification also represents a resent notification. So, when a notification is resent, a new one is created that references the original Notification as a parent. This Notification is also created by the `NotificationService` class.
|
||||
|
||||
### Notification Entity Overview
|
||||
|
||||
The two most important properties in the [`Notification`](../../references/entities/classes/entities.Notification.mdx) entity are the `to` and `data` properties.
|
||||
|
||||
The `to` property is a string that represents the receiver of the Notification. For example, if the Notification was sent to an email address, the `to` property holds the email address the Notification was sent to.
|
||||
|
||||
The `to` property can alternatively be a phone number or a chat username. It depends on the Notification Provider and how it sends the Notification.
|
||||
|
||||
The `data` property is an object that holds all the data necessary to send the Notification. For example, in the case of an order confirmation Notification, it can hold data related to the order.
|
||||
|
||||
The `data` property is useful when a notification is resent later. The same `data` can be used to resend the notification.
|
||||
|
||||
In the case of resent notifications, the resent notification has a `parent_id` set to the ID of the original Notification. The value of the `parent_id` property in the original Notification is `null`.
|
||||
|
||||
The `Notification` entity has some properties that determine the context of this Notification. This includes the `event_name` property which is the event that triggered the sending of this notification.
|
||||
|
||||
Additionally, the `resource_type` property is used to determine what resource this event is associated with. For example, if the `event_name` is `order.placed`, the `resource_type` is `order`.
|
||||
|
||||
You can also access the specific resource using the `resource_id` property, which is the ID of the resource. So, in case of the `order.placed` event, the `resource_id` is the ID of the order that was created.
|
||||
|
||||
The `Notification` entity also includes properties related to the receiver of the Notification. In case the receiver is a customer, the `customer_id` property is used to identify which customer.
|
||||
|
||||
---
|
||||
|
||||
## Automating Flows with Notifications
|
||||
|
||||
With Medusa you can create notifications as a reaction to a wide spectrum of events, allowing you to automate communication and processes.
|
||||
|
||||
An example of a flow that can be implemented using Medusa's Notification API is automated return flows:
|
||||
|
||||
- A customer requests a return by sending a `POST` request to the `/store/returns` API Route.
|
||||
- The Notification Provider listens to the `order.return_requested` event and sends an email to the customer with a return invoice and return label generated by the Fulfillment Provider.
|
||||
- The customer returns the items triggering the `return.received` event.
|
||||
- The Notification Provider listens to the `return.received` event and sends an email to the customer with confirmation that their items have been received and that a refund has been issued.
|
||||
|
||||
---
|
||||
|
||||
## Custom Development
|
||||
|
||||
Developers can create custom notification providers in the Medusa backend, a plugin, or in a module.
|
||||
|
||||
<DocCard item={{
|
||||
type: 'link',
|
||||
href: '/development/notification/create-notification-provider',
|
||||
label: 'Create a Notification Provider',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a notification provider in Medusa.'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@@ -1,210 +0,0 @@
|
||||
---
|
||||
description: "Learn about development with Medusa, fundamental concepts, and more."
|
||||
hide_table_of_contents: true
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Medusa Development
|
||||
|
||||
This part of the documentation provides you with the fundamental concepts and guides that can help you build and customize commerce applications with Medusa.
|
||||
|
||||
## Introduction
|
||||
|
||||
Medusa is a set of tools that developers can use to build digital commerce applications. Whether you want to offer unique customer experiences, create powerful automations, or build rich commerce applications like marketplaces, Medusa provides all the necessary tools.
|
||||
|
||||
Other ecommerce platforms offer a finite set of features accessible through an API. Medusa is different because it provides building blocks for businesses and developers to build commerce features. This means that you can extend your commerce API for your exact needs.
|
||||
|
||||
Medusa's building blocks ship as NPM packages of the following types:
|
||||
|
||||
- [Commerce modules](../modules/overview.mdx), which are isolated commerce logic for different domains. For example, an Inventory Module.
|
||||
- A core package responsible for orchestrating the different commerce modules and exposing REST APIs.
|
||||
|
||||
---
|
||||
|
||||
## How Does Medusa Work
|
||||
|
||||
The core package is the NPM package `@medusajs/medusa`. It's a Node.js server built with Express and other tools that offer functionalities for managing events, caching, job queues, and more.
|
||||
|
||||
The core package has two main objectives.
|
||||
|
||||
### Orchestrate Commerce Modules
|
||||
|
||||
When you build a commerce application with Medusa, you’ll typically interact with more than one commerce module. The core package manages relationships between modules, and forwarding calls to modules at the right time during business logic execution.
|
||||
|
||||
For example, imagine an Inventory module that contains lightweight logic to increment and decrement stock levels for a Stock-Keeping Unit (SKU). In a commerce application, you typically want to associate the stock levels with a specific product. Medusa offers both an Inventory module and a Product module, and the core package creates associations between these modules and executing the related business logic. So, the core package contains code similar to this:
|
||||
|
||||
```ts
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
export const POST = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
// ...
|
||||
|
||||
// associate a product with an inventory item
|
||||
const product = await productService.create(data)
|
||||
const inventoryItem = await inventoryService.create(
|
||||
inventoryData
|
||||
)
|
||||
await productVariantInventoryService.associate(
|
||||
product.id,
|
||||
inventoryItem.id
|
||||
)
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Expose REST APIs
|
||||
|
||||
The goal of orchestrating the modules is to expose an API that client applications, like websites or apps, can consume. By default, Medusa’s core package exposes a REST API that offers commerce functionalities similar to what other platforms give you.
|
||||
|
||||
The core package also holds the logic that allows developers to extend and add custom API Routes, among other available customizations.
|
||||
|
||||
---
|
||||
|
||||
## Backend First Steps
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/backend/install',
|
||||
label: 'Backend Quickstart',
|
||||
customProps: {
|
||||
icon: Icons['server-stack-solid'],
|
||||
description: 'Learn how to install a Medusa backend and the available next steps.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/backend/prepare-environment',
|
||||
label: 'Prepare Environment',
|
||||
customProps: {
|
||||
icon: Icons['tools-solid'],
|
||||
description: 'Learn how to install the necessary tools to use Medusa.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/backend/configurations',
|
||||
label: 'Configure Backend',
|
||||
customProps: {
|
||||
icon: Icons['cog-six-tooth-solid'],
|
||||
description: 'Learn how to configure the Medusa backend.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Understanding Fundamental Concepts
|
||||
|
||||
These concepts will guide you through your development and building customization with Medusa.
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/entities/overview',
|
||||
label: 'Entities',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'A class representation of database tables to handle commerce data.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/api-routes/overview',
|
||||
label: 'API Routes',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'REST APIs that frontends consume to communicate with the backend.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/services/overview',
|
||||
label: 'Services',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Utility classes associated with Entities or a specific functionality.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/events',
|
||||
label: 'Events and Subscribers',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Perform an asynchronous task when an action occurs.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/scheduled-jobs/overview',
|
||||
label: 'Scheduled Jobs',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Automate tasks to be executed at specified times.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/plugins/overview',
|
||||
label: 'Plugins',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Publish customizations as NPM packages to be reused.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Medusa Use Cases
|
||||
|
||||
To better understand how you can use Medusa, here are some common use cases that Medusa is the ideal solution for.
|
||||
|
||||
### Ecommerce Building Blocks
|
||||
|
||||
Developers can set up the core package and handpick the commerce modules they want to use. This gives them great flexibility in choosing the features they want to provide in their ecommerce store, while utilizing the powerful architecture in the core package.
|
||||
|
||||

|
||||
|
||||
Developers can modify and tailor the modules that Medusa offers to their use case. They can also create custom Modules to implement new features. All these modules become building blocks that shape their ecommerce system.
|
||||
|
||||
### Medusa in Microservices Architectures
|
||||
|
||||
Medusa’s commerce modules can be used in isolation from the core package and within a larger ecosystem. For example, you can use Medusa’s Cart module within a blog to allow readers to buy merch.
|
||||
|
||||

|
||||
|
||||
Developers can benefit from Medusa’s Modules that provide essential ecommerce features while maintaining the ecommerce ecosystem of their choice. Commerce modules can be installed in your setup as NPM packages.
|
||||
|
||||
### Vertical Ecommerce Platforms
|
||||
|
||||
A Vertical Ecommerce Platform is a platform that provides features and functionalities specialized for a type of business sector. For example, a platform for pharmaceuticals.
|
||||
|
||||
Developers can use Medusa to build a vertical ecommerce platform as it provides the stepping stones that eliminate the need to reinvent the wheel for basic ecommerce features, but are customizable enough to be changed for their use case.
|
||||
|
||||
### Out-of-Box APIs
|
||||
|
||||
Since Medusa’s commerce modules are NPM packages, they can be installed and used in any JavaScript project.
|
||||
|
||||
By installing a Module in your project and expose its APIs based on the framework you’re using, you can get ecommerce REST APIs right from your frontend framework without having to create a separate project.
|
||||
|
||||
### Full-Fledged Ecommerce System
|
||||
|
||||
Developers can use Medusa’s toolkit to create their ecommerce system. With the use of the [create-medusa-app](../create-medusa-app.mdx) command, developers can set up a Medusa Backend, Medusa Admin, and a storefront.
|
||||
|
||||

|
||||
|
||||
Developers can still benefit from customization opportunities here that Medusa provides. This includes creating resources such as API Routes and services, creating plugins, integrating third-party services, create a custom storefront, and more.
|
||||
|
||||
### Your Own Use Case
|
||||
|
||||
Medusa’s vision is to allow developers to build anything they want using it. There are no limitations to what you can build and the ideas you can come up with. If you have an idea, you can use Medusa’s tools to start building it.
|
||||
@@ -1,585 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to create a plugin in Medusa. This guide explains how to develop, configure, and test a plugin.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import Icons from '@theme/Icon';
|
||||
import LearningPath from '@site/src/components/LearningPath';
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Create a Plugin
|
||||
|
||||
In this document, you’ll learn how to create a plugin and some tips for development. If you’re interested to learn more about what plugins are and where to find available official and community plugins, check out the [overview document](./overview.mdx).
|
||||
|
||||
Alternatively, you can follow this recipe to create a plugin with step-by-step guidance.
|
||||
|
||||
<LearningPath pathName="plugin" />
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You must have an existing Medusa project that you want to create the plugin with.
|
||||
|
||||
The recommended way to create a plugin is using the `new` command from Medusa CLI:
|
||||
|
||||
```bash
|
||||
npx @medusajs/medusa-cli@latest new medusa-plugin-custom
|
||||
```
|
||||
|
||||
Where `medusa-plugin-custom` is the name of the plugin you’re creating. In Medusa, plugins are named based on their functionalities.
|
||||
|
||||
By convention, all plugin names start with `medusa` followed by a descriptive name of what the plugin does. For example, the Stripe plugin is named `medusa-payment-stripe`.
|
||||
|
||||
---
|
||||
|
||||
## Changes to package.json
|
||||
|
||||
### Package Name
|
||||
|
||||
By default, your package name in `package.json` will be `medusa-starter-default`. This should instead be the name of your plugin. For example, the Stripe plugin's package name is `medusa-payment-stripe`.
|
||||
|
||||
### Change Dependencies
|
||||
|
||||
A basic Medusa backend installed with the `medusa new` command has dependencies that are necessary for the backend, but not necessary for plugins.
|
||||
|
||||
For example, can remove the dependencies `medusa-fulfillment-manual`, `medusa-payment-manual`, and `medusa-payment-stripe` as they are fulfillment and payment plugins necessary for a Medusa backend, but not for a plugin. The same goes for modules like `@medusajs/cache-inmemory`.
|
||||
|
||||
Additionally, you can remove `@medusajs/medusa-cli` as you don’t need to use the Medusa CLI while developing a plugin.
|
||||
|
||||
You should also add `@medusajs/medusa` as a peer dependency:
|
||||
|
||||
```json
|
||||
"peerDependencies": {
|
||||
"@medusajs/medusa": "YOUR_MEDUSA_VERSION",
|
||||
// other peer dependencies...
|
||||
}
|
||||
```
|
||||
|
||||
Where `YOUR_MEDUSA_VERSION` is the version you're using of the Medusa core package. You should be able to find it under `devDependencies`.
|
||||
|
||||
Once you’re done making these changes, re-run the install command to update your `node_modules` directory:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install
|
||||
```
|
||||
|
||||
Then, make sure to remove the plugins and modules you removed from `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
// previously had plugins
|
||||
const plugins = []
|
||||
|
||||
// previously had modules
|
||||
const modules = {}
|
||||
```
|
||||
|
||||
### Changes for Admin Plugins
|
||||
|
||||
If your plugin contains customizations to the admin dashboard, it's recommended to create different `tsconfig` files for backend and admin customizations, then modify the scripts in `package.json` to handle building backend and admin customizations separately.
|
||||
|
||||
:::note
|
||||
|
||||
These changes may already be available in your Medusa project. They're included here for reference purposes.
|
||||
|
||||
:::
|
||||
|
||||
Start by updating your `tsconfig.json` with the following configurations:
|
||||
|
||||
```json title="tsconfig.json"
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2019",
|
||||
"module": "commonjs",
|
||||
"allowJs": true,
|
||||
"checkJs": false,
|
||||
"jsx": "react-jsx",
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"noEmit": false,
|
||||
"strict": false,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/"],
|
||||
"exclude": [
|
||||
"dist",
|
||||
"build",
|
||||
".cache",
|
||||
"tests",
|
||||
"**/*.spec.js",
|
||||
"**/*.spec.ts",
|
||||
"node_modules",
|
||||
".eslintrc.js"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The important changes to note here are the inclusion of the field `"jsx": "react-jsx"` and the addition of `"build"` and `“.cache”` to `exclude`.
|
||||
|
||||
The addition of `"jsx": "react-jsx"` specified how should TypeScript transform JSX, and excluding `build` and `.cache` ensures that TypeScript ignores build and development files.
|
||||
|
||||
Next, create the file `tsconfig.server.json` with the following content:
|
||||
|
||||
```json title="tsconfig.server.json"
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
/* Emit a single file with source maps instead of having a separate file. */
|
||||
"inlineSourceMap": true
|
||||
},
|
||||
"exclude": ["src/admin", "**/*.spec.js"]
|
||||
}
|
||||
```
|
||||
|
||||
This is the configuration that will be used to transpile your custom backend code, such as services or entities. The important part is that it excludes `src/admin` as that is where your Admin code will live.
|
||||
|
||||
Then, create the file `tsconfig.admin.json` with the following content:
|
||||
|
||||
```json title="tsconfig.admin.json"
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "esnext"
|
||||
},
|
||||
"include": ["src/admin"],
|
||||
"exclude": ["**/*.spec.js"]
|
||||
}
|
||||
```
|
||||
|
||||
This is the configuration that will be used when transpiling your admin code.
|
||||
|
||||
Finally, update the `build` scripts in your project and add a new `prepare` command:
|
||||
|
||||
```json title="package.json"
|
||||
"scripts": {
|
||||
// other scripts...
|
||||
"build": "cross-env npm run clean && npm run build:server && npm run build:admin",
|
||||
"build:server": "cross-env npm run clean && tsc -p tsconfig.json",
|
||||
"build:admin": "cross-env medusa-admin build",
|
||||
"prepare": "cross-env NODE_ENV=production npm run build:server && medusa-admin bundle"
|
||||
}
|
||||
```
|
||||
|
||||
Each of these scripts do the following:
|
||||
|
||||
- `build`: used to build resources for both admin and backend for development. You'll typically use this script during your plugin development.
|
||||
- `build:server`: used to build backend resources for development.
|
||||
- `build:admin`: used to build admin resources for development.
|
||||
- `prepare`: used to build resources for publishing. You'll typically use this script during plugin testing and publishing.
|
||||
|
||||
Furthermore, make sure to add `react` to `peerDependencies` along with `react-router-dom` if you're using it:
|
||||
|
||||
```json title="package.json"
|
||||
"peerDependencies": {
|
||||
// other dependencies...
|
||||
"react": "^18.2.0",
|
||||
"react-router-dom": "^6.13.0"
|
||||
}
|
||||
```
|
||||
|
||||
### Delete Irrelevant Files
|
||||
|
||||
If you've installed the Medusa backend using the [create-medusa-app](../../create-medusa-app.mdx) command, you might find files under the `src` sub-directories that aren't necessary for your plugin development. For example, `src/model/onboarding.ts` or migrations under the `src/migrations` directory.
|
||||
|
||||
Make sure to delete these files if you're not using them in your plugin.
|
||||
|
||||
---
|
||||
|
||||
## Plugin Development
|
||||
|
||||
### Plugin Structure
|
||||
|
||||
While developing your plugin, you can create your TypeScript or JavaScript files under the `src` directory. This includes creating services, API Routes, migrations, and other resources.
|
||||
|
||||
However, before you test the changes on a Medusa backend or publish your plugin, you must transpile your files and move them either to a `dist` directory or to the root of the plugin's directory.
|
||||
|
||||
For example, if you have an API Route in `src/api/store/custom/route.ts`, after running the `build` or `watch` commands [as defined earlier](#recommended-change-scripts), the file should be transpiled into `dist/api/store/custom/route.ts` in your plugin's root. You can alternative transpile them into the `api/store/custom/route.ts` in your plugin's root.
|
||||
|
||||
### Development Resources
|
||||
|
||||
This guide doesn't cover how to create different files and components. If you’re interested in learning how to do that, you can check out these guides:
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/entities/create',
|
||||
label: 'Create an Entity',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create an entity.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/services/create-service',
|
||||
label: 'Create a Service',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a service.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/api-routes/create',
|
||||
label: 'Create an API Route',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create an API Route.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/events/create-subscriber',
|
||||
label: 'Create a Subscriber',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a subscriber.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/admin/widgets',
|
||||
label: 'Create an Admin Widget',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create an admin widget.',
|
||||
badge: {
|
||||
variant: 'orange',
|
||||
children: 'Beta'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/admin/routes',
|
||||
label: 'Create an Admin UI Route',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create an admin UI route.',
|
||||
badge: {
|
||||
variant: 'orange',
|
||||
children: 'Beta'
|
||||
}
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
If you're developing something specific, such as a payment processor plugin, you can follow one of the following guides to learn how to create different services within your plugin.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/backend/add-payment-provider',
|
||||
label: 'Create a Payment Processor',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a payment processor.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/backend/add-fulfillment-provider',
|
||||
label: 'Create a Fulfillment Provider',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a fulfillment provider.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/search/create',
|
||||
label: 'Create a Search Service',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a search service.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/file-service/create-file-service',
|
||||
label: 'Create a File Service',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a file service.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/notification/create-notification-provider',
|
||||
label: 'Create a Notification Service',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a notification service.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
### Plugin Options
|
||||
|
||||
Plugins often allow developers that will later use them to provide their own option. For example, you can allow developers to specify the API key of a service you’re integrating.
|
||||
|
||||
Developers that use your plugin will pass options to your plugin in the `plugins` array in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: `medusa-plugin-custom`,
|
||||
options: {
|
||||
name: "My Store",
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
In your plugin's services, you can have access to the option in their constructor. The options are passed as a second parameter to the `constructor` method.
|
||||
|
||||
For example:
|
||||
|
||||
```js title="src/service/my.ts"
|
||||
// In a service in your plugin
|
||||
class MyService extends TransactionBaseService {
|
||||
constructor(container, options) {
|
||||
super(container)
|
||||
// options contains plugin options
|
||||
this.name = options.name
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
:::tip
|
||||
|
||||
Make sure to include in the README of your plugin the options that can be passed to a plugin.
|
||||
|
||||
:::
|
||||
|
||||
### enableUI Plugin Option
|
||||
|
||||
All plugins accept an option named `enableUI`. This option is useful mainly if your plugin contains admin customizations. It allows users to enable or disable admin customizations in the admin dashboard.
|
||||
|
||||
A developer using your plugin can pass the `enableUI` option as part of the plugin's options:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: `medusa-plugin-custom`,
|
||||
options: {
|
||||
// other options
|
||||
enableUI: true,
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
If you're passing your plugin options to third-party services, make sure to omit it from the plugin options you receive in your resources, such as services. The `enableUI` option will always be passed as part of your plugin options.
|
||||
|
||||
For example:
|
||||
|
||||
```js title="src/service/test.ts"
|
||||
// In a service in your plugin
|
||||
class MyService extends TransactionBaseService {
|
||||
constructor(container, options) {
|
||||
super(container)
|
||||
// options contains plugin options
|
||||
const { enableUI, ...otherOptions } = options
|
||||
// pass otherOptions to a third-party service
|
||||
const client = new Client(otherOptions)
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
`enableUI`'s default value is `false` if not provided by the plugin users. This means that it must be enabled manually in a plugin's option for the customizations to appear in the admin dashboard.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Test Your Plugin
|
||||
|
||||
While you develop your plugin, you’ll need to test it on an actual Medusa backend. This can be done using the [npm link](https://docs.npmjs.com/cli/v8/commands/npm-link) command.
|
||||
|
||||
### Step 1: Build Changes
|
||||
|
||||
<Tabs groupId="plugin-preference">
|
||||
<TabItem value="without-admin" label="Without Admin Customizations" default>
|
||||
In the root of your plugin directory, run the `build` command:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="with-admin" label="With Admin Customizations">
|
||||
In the root of your plugin directory, run the `prepare` command:
|
||||
|
||||
```bash
|
||||
npm run prepare
|
||||
```
|
||||
|
||||
If the `prepare` script is not available in your project, you can find it in [this section](#changes-for-admin-plugins).
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Step 2: Link Package
|
||||
|
||||
In the root of your plugin directory, run the following command:
|
||||
|
||||
```bash npm2yarn
|
||||
npm link
|
||||
```
|
||||
|
||||
Then, in the directory of the Medusa backend you want to test the plugin on, run the following command:
|
||||
|
||||
```bash npm2yarn
|
||||
npm link medusa-plugin-custom
|
||||
```
|
||||
|
||||
Where `medusa-plugin-custom` is the package name of your plugin.
|
||||
|
||||
### Step 3: Remove Medusa Dependency
|
||||
|
||||
As your plugin has the `@medusajs/medusa` package installed, and the Medusa backend has `@medusajs/medusa` installed as well, this can cause dependency errors.
|
||||
|
||||
To avoid that, remove the `@medusajs` directory from the `node_modules` of your plugin's directory. For Unix-based operating systems you can use the following command:
|
||||
|
||||
```bash
|
||||
rm -rf node_modules/@medusajs
|
||||
```
|
||||
|
||||
### Step 4: Add Plugin to Configurations
|
||||
|
||||
In the `medusa-config.js` file of the Medusa backend you're testing the plugin on, add your custom plugin to the `plugins` array:
|
||||
|
||||
```js
|
||||
const plugins = [
|
||||
// other plugins...
|
||||
{
|
||||
resolve: `medusa-plugin-custom`,
|
||||
options: {
|
||||
// plugin options...
|
||||
// if plugin has admin customizations:
|
||||
enableUI: true,
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
Make sure to change `medusa-plugin-custom` with the name of your plugin. Also, if your plugin has admin customizations, make sure to include the [enableUI](#enableui-plugin-option) option.
|
||||
|
||||
### (Optional) Step 5: Run Migrations
|
||||
|
||||
If your plugin includes migrations, run the following command in the Medusa backend's directory:
|
||||
|
||||
```bash
|
||||
npx medusa migrations run
|
||||
```
|
||||
|
||||
### Step 6: Run the Medusa Backend
|
||||
|
||||
In the directory of the Medusa backend, start the backend with the `dev` command passing it the `--preserve-symlinks` option:
|
||||
|
||||
<Tabs groupId="npm2yarn" isCodeTabs={true}>
|
||||
<TabItem value="npm" label="npm" default>
|
||||
|
||||
```bash
|
||||
npm run dev -- -- --preserve-symlinks
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn-1" label="Yarn v1">
|
||||
|
||||
```bash
|
||||
yarn dev -- -- --preserve-symlinks
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn-berry" label="Yarn berry">
|
||||
|
||||
```bash
|
||||
yarn dev -- --preserve-symlinks
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="pnpm" label="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm run dev -- -- --preserve-symlinks
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Making Changes to the Plugin
|
||||
|
||||
While testing your plugin, if you need to make changes you need to re-install the plugin's dependencies:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install
|
||||
```
|
||||
|
||||
Then, after making the changes, run the steps [one](#step-1-build-changes), [three](#step-3-remove-medusa-dependency), and [six](#step-6-run-the-medusa-backend) mentioned above.
|
||||
|
||||
### Troubleshoot Errors
|
||||
|
||||
#### Error: The class must be a valid service implementation
|
||||
|
||||
Please make sure that your plugin is following the correct structure. If the error persists then please try the following fix:
|
||||
|
||||
```bash npm2yarn
|
||||
cd <BACKEND_PATH>/node_modules/medusa-interfaces
|
||||
npm link
|
||||
cd <BACKEND_PATH>/node_modules/@medusajs/medusa
|
||||
npm link
|
||||
cd <PLUGIN_PATH>
|
||||
rm -rf node_modules/medusa-interfaces
|
||||
rm -rf node_modules/@medusajs/medusa
|
||||
npm link medusa-interfaces
|
||||
npm link @medusajs/medusa
|
||||
npm link
|
||||
cd <BACKEND_PATH>
|
||||
npm link your-plugin
|
||||
```
|
||||
|
||||
Where `<BACKEND_PATH>` is the path to your Medusa backend and `<PLUGIN_PATH>` is the path to your plugin.
|
||||
|
||||
This links the `medusa-interfaces` and `@medusajs/medusa` packages from your `medusa-backend` to your plugin directory and then links your plugin to your `medusa-backend`.
|
||||
|
||||
#### APIs not loading
|
||||
|
||||
If the APIs you added to your Medussa backend are not loading then please try the following steps:
|
||||
|
||||
```bash npm2yarn
|
||||
cd <PLUGIN_PATH>
|
||||
rm -rf node_modules
|
||||
cd <BACKEND_PATH>/node_modules/<PLUGIN_NAME>
|
||||
npm install
|
||||
cd <PLUGIN_PATH>
|
||||
npm run build
|
||||
cd <BACKEND_PATH>
|
||||
npm run start
|
||||
```
|
||||
|
||||
Where `<BACKEND_PATH>` is the path to your Medusa backend, `<PLUGIN_PATH>` is the path to your plugin and `<PLUGIN_NAME>` is the name of your plugin as it is in your plugin `package.json` file.
|
||||
|
||||
---
|
||||
|
||||
## Publish Plugin
|
||||
|
||||
Once you're done with the development of the plugin, you can publish it to NPM so that other Medusa developers and users can use it.
|
||||
|
||||
Please refer to [this guide on required steps to publish a plugin](./publish.mdx).
|
||||
@@ -1,117 +0,0 @@
|
||||
---
|
||||
description: 'Learn what Plugins are and how they are used in Medusa. Plugins are re-usable customizations that can be added to a Medusa backend.'
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import Icons from '@theme/Icon';
|
||||
import LearningPath from '@site/src/components/LearningPath';
|
||||
|
||||
# Plugins
|
||||
|
||||
In this document, you’ll get an overview of plugins in Medusa, where to find them, and how to install them. If you want to learn how to create a plugin, check out [this guide](create.mdx) instead.
|
||||
|
||||
## Overview
|
||||
|
||||
Medusa was built with flexibility and extendability in mind. All different components and functionalities in Medusa are built with an abstraction layer that gives developers the freedom to choose what services they want to use or how to implement a certain component in their ecommerce store.
|
||||
|
||||
Developers can use plugins to take advantage of this abstraction, flexibility, and extendibility. Plugins allow developers to implement custom features or integrate third-party services into Medusa.
|
||||
|
||||
For example, if you want to use Stripe as a payment processor in your store, then you can install the Stripe plugin on your backend and use it.
|
||||
|
||||
An alternative approach is developing a custom way of handling payment on your ecommerce store. Both approaches are achievable by either creating a plugin or using an existing plugin.
|
||||
|
||||
Plugins run within the same process as the core Medusa backend eliminating the need for extra backend capacity, infrastructure, and maintenance. As a result, plugins can use all other services as dependencies and access the database.
|
||||
|
||||
Plugins can contain customizations to the Medusa backend or the admin dashboard.
|
||||
|
||||
<LearningPath pathName="plugin" />
|
||||
|
||||
---
|
||||
|
||||
## Using Existing Plugins
|
||||
|
||||
### Official Plugins
|
||||
|
||||
Medusa has official plugins that cover different aspects and functionalities such as payment, Content Management System (CMS), fulfillment, and notifications. You can check out the available plugins under the [Plugins section of this documentation](../../plugins/overview.mdx).
|
||||
|
||||
:::tip
|
||||
|
||||
To feature your plugin in our repository, you can send a pull request that adds your plugin into the `packages` directory. Our team will review your plugin and, if approved, will merge the pull request and add your plugin in the repository.
|
||||
|
||||
:::
|
||||
|
||||
### Community Plugins
|
||||
|
||||
You can find community plugins by [searching NPM for the `medusa` or `medusa-plugin` keywords](https://www.npmjs.com/search?q=keywords%3Amedusa%2Cmedusa-plugin).
|
||||
|
||||
You can also check [our Plugin directory](https://medusajs.com/plugins/) for a list of existing Medusa plugins.
|
||||
|
||||
---
|
||||
|
||||
## How to Install a Plugin
|
||||
|
||||
To install an existing plugin, in your Medusa backend run the following command:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install <plugin_name>
|
||||
```
|
||||
|
||||
Where `<plugin_name>` is the package name of the plugin. For example, if you’re installing the Stripe plugin `<plugin_name>` is `medusa-payment-stripe`.
|
||||
|
||||
### Plugin Configuration
|
||||
|
||||
If you’re installing an official plugin from the Medusa repository, you can find in its `README.md` file a list of configurations that are either required or optional. You can also refer to the documentation related to that plugin for more details on how to install, configure, and use it.
|
||||
|
||||
For community plugins, please refer to the installation instructions of that plugin to learn about any required configurations.
|
||||
|
||||
### enableUI Plugin Option
|
||||
|
||||
All plugins accept an option named `enableUI`. This option allows you to disable admin customizations from appearing in the admin dashboard.
|
||||
|
||||
:::note
|
||||
|
||||
`enableUI`'s default value is `false` if not provided by the plugin users. This means that it must be enabled manually in a plugin's configuration for the customizations to appear in the admin dashboard.
|
||||
|
||||
:::
|
||||
|
||||
You can set the `enableUI` value by passing it as part of the plugin's configurations:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: `medusa-plugin-custom`,
|
||||
options: {
|
||||
// other options
|
||||
enableUI: true,
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Custom Development
|
||||
|
||||
Developers can create plugins and reuse them across different Medusa backends. They can also share them with the community to help out other developers.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/plugins/create',
|
||||
label: 'Create a Plugin',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create plugins in Medusa.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/plugins/publish',
|
||||
label: 'Publish a Plugin',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to publish a plugin to NPM.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
@@ -1,178 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to publish a Medusa plugin to NPM. This guide lists some check lists to ensure you have implemented before publishing, as well as required steps.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Publish a Plugin
|
||||
|
||||
In this document, you'll learn how to publish a Medusa plugin to NPM and what are some requirements to keep in mind before publishing. Afterwards, your plugin will be published on the [Medusa Plugins page](https://medusajs.com/plugins/).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
If you haven't created a plugin yet, please check [this guide to learn how to create a plugin](./create.mdx).
|
||||
|
||||
---
|
||||
|
||||
## Prepare Plugin
|
||||
|
||||
### package.json Checklist
|
||||
|
||||
Before publishing your plugin, make sure you've set the following fields in your plugin's package.json:
|
||||
|
||||
- `name`: The name of your plugin. By convention, all plugin names start with `medusa` followed by a descriptive name of what the plugin does. For example, `medusa-payment-stripe`.
|
||||
- `description`: A short description of what the plugin does.
|
||||
- `author`: Your name or your company's name.
|
||||
- `repository`: This includes details about the repository that holds the source code of the plugin. It's an object that holds the following properties:
|
||||
- `type`: Should be `git`.
|
||||
- `url`: The URL to the repository (for example, the GitHub repository holding the code of your plugin).
|
||||
- `keywords`: An array of keywords that are related to the plugin. It's required for all Medusa plugins to use the keywords `medusa-plugin`. Other recommended keywords are:
|
||||
- `medusa-plugin-analytics`: For plugins that add analytics functionalities or integrations.
|
||||
- `medusa-plugin-cms`: For plugins that add CMS functionalities or integrations.
|
||||
- `medusa-plugin-notification`: For plugins that add notification functionalities or integrations.
|
||||
- `medusa-plugin-payment`: For plugins that add payment functionalities or integrations.
|
||||
- `medusa-plugin-search`: For plugins that add search functionalities or integrations.
|
||||
- `medusa-plugin-shipping`: For plugins that add shipping functionalities or integrations.
|
||||
- `medusa-plugin-storage`: For plugins that add a file service or storage integration.
|
||||
- `medusa-plugin-source`: For plugins that help migrate or import data into Medusa from another platform.
|
||||
- `medusa-plugin-storefront`: For storefronts that can be integrated with a Medusa backend.
|
||||
- `medusa-plugin-admin`: For plugins that include only admin customizations.
|
||||
- `medusa-plugin-other`: For any other type of plugin.
|
||||
|
||||
### Scripts in package.json
|
||||
|
||||
<Tabs groupId="plugin-preference">
|
||||
<TabItem value="without-admin" label="Without Admin Customizations" default>
|
||||
Make sure you add the `publish` script to your `scripts` field:
|
||||
|
||||
```json title="package.json"
|
||||
"scripts": {
|
||||
// other scripts...
|
||||
"build": "cross-env npm run clean && tsc -p tsconfig.json",
|
||||
"prepare": "cross-env NODE_ENV=production npm run build"
|
||||
}
|
||||
```
|
||||
|
||||
The `build` script ensures that the plugin's built files are placed as explained in the [plugin structure](./create.mdx#plugin-structure) section of the Create Plugin documentation.
|
||||
|
||||
The `prepare` script facilitates your publishing process. You would typically run this script before publishing your plugin.
|
||||
</TabItem>
|
||||
<TabItem value="with-admin" label="With Admin Customizations">
|
||||
First, make sure to change `tsconfig` files as recommended in the [create guide](./create.mdx#changes-for-admin-plugins).
|
||||
|
||||
Then, add the following `prepare` and `build` scripts to your `scripts`
|
||||
|
||||
```json title="package.json"
|
||||
"scripts": {
|
||||
// other scripts...
|
||||
"build:server": "cross-env npm run clean && tsc -p tsconfig.json",
|
||||
"prepare": "cross-env NODE_ENV=production npm run build:server && medusa-admin bundle"
|
||||
}
|
||||
```
|
||||
|
||||
The `build:server` script builds the resources of the backend for development and ensures they are placed as explained in the [plugin structure](./create.mdx#plugin-structure) section of the Create Plugin documentation.
|
||||
|
||||
The `prepare` script creates a production build of both backend and admin resources.
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Plugin Structure
|
||||
|
||||
Make sure your plugin's structure is as described in the [Create Plugin](./create.mdx#plugin-structure) documentation. If you've made the changes mentioned in [the above section to the scripts](#scripts-in-packagejson) in `package.json`, you should have the correct structure when you run the `prepare` command.
|
||||
|
||||
### NPM Ignore File
|
||||
|
||||
Not all files that you use while developing your plugin are necessary to be published.
|
||||
|
||||
For example, the files you add in the `src` directory are compiled to the root of the plugin directory before publishing. Then, when a developer installs your plugin, they’ll just be using the files in the root.
|
||||
|
||||
So, you can ignore files and directories like `src` from the final published NPM package.
|
||||
|
||||
To do that, create the file `.npmignore` with the following content:
|
||||
|
||||
```bash title=".npmignore"
|
||||
/lib
|
||||
node_modules
|
||||
.DS_store
|
||||
.env*
|
||||
/*.js
|
||||
!index.js
|
||||
yarn.lock
|
||||
src
|
||||
.gitignore
|
||||
.eslintrc
|
||||
.babelrc
|
||||
.prettierrc
|
||||
build
|
||||
.cache
|
||||
.yarn
|
||||
uploads
|
||||
|
||||
# These are files that are included in a
|
||||
# Medusa project and can be removed from a
|
||||
# plugin project
|
||||
medusa-config.js
|
||||
Dockerfile
|
||||
medusa-db.sql
|
||||
develop.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Publish Plugin
|
||||
|
||||
This section explains how to publish your plugin to NPM.
|
||||
|
||||
Before you publish a plugin, you must [create an account on NPM](https://www.npmjs.com/signup).
|
||||
|
||||
### Step 1: Run Prepare Command
|
||||
|
||||
Before you publish or update your plugin, make sure to run the `prepare` command [defined earlier](#packagejson-checklist):
|
||||
|
||||
```bash npm2yarn
|
||||
npm run prepare
|
||||
```
|
||||
|
||||
### Step 2: Publish Plugin Package
|
||||
|
||||
You can publish your package with the following command:
|
||||
|
||||
```bash
|
||||
npm publish
|
||||
```
|
||||
|
||||
If you haven't logged in before with your NPM account, you'll be asked to login first.
|
||||
|
||||
Your package is then published on NPM and everyone can use it and install it.
|
||||
|
||||
---
|
||||
|
||||
## Install Plugin
|
||||
|
||||
To install your published plugin, run the following command on any Medusa backend project:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install medusa-plugin-custom
|
||||
```
|
||||
|
||||
Where `medusa-plugin-custom` is your plugin's package name.
|
||||
|
||||
---
|
||||
|
||||
## Update Plugin
|
||||
|
||||
If you make changes to your plugin and you want to publish those changes, run the following command to change the NPM version:
|
||||
|
||||
```bash
|
||||
npm version <type>
|
||||
```
|
||||
|
||||
Where `<type>` indicates the type of version update you’re publishing. For example, it can be `major` or `minor`. You can see the [full list of types in NPM’s documentation](https://docs.npmjs.com/cli/v8/commands/npm-version).
|
||||
|
||||
Then, publish the new update:
|
||||
|
||||
```bash
|
||||
npm publish
|
||||
```
|
||||
-830
@@ -1,830 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to implement publishable API key functionalities for admins in Medusa using the REST APIs. This includes how to list, create, update, and delete a publishable API key.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Manage Publishable API Keys
|
||||
|
||||
In this document, you’ll learn how to manage the publishable API keys using the admin APIs.
|
||||
|
||||
## Overview
|
||||
|
||||
Publishable API keys allow you to associate a set of resources with an API key. You can then pass that API key in storefront requests to guarantee that processed or returned data is within the scope you defined when creating the API key.
|
||||
|
||||
Currently, publishable API keys can only be associated with sales channels.
|
||||
|
||||
Using the Admin APIs, you can manage your Publishable API Keys.
|
||||
|
||||
:::note
|
||||
|
||||
Publishable API keys are only for client-side use. They can be publicly accessible in your code, as they are not authorized for the Admin API.
|
||||
|
||||
:::
|
||||
|
||||
### Scenario
|
||||
|
||||
You want to use or implement the following admin functionalities:
|
||||
|
||||
- Manage publishable keys including listing, creating, updating, and deleting them.
|
||||
- Manage sales channels associated with a publishable API key including listing, adding, and deleting them from the publishable API key.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Components
|
||||
|
||||
It is assumed that you already have a Medusa backend installed and set up. If not, you can follow the [quickstart guide](../../backend/install.mdx) to get started.
|
||||
|
||||
### JS Client
|
||||
|
||||
This guide includes code snippets to send requests to your Medusa backend using Medusa’s JS Client, among other methods.
|
||||
|
||||
If you follow the JS Client code blocks, it’s assumed you already have [Medusa’s JS Client](../../../js-client/overview.mdx) installed and have [created an instance of the client](../../../js-client/overview.mdx#configuration).
|
||||
|
||||
### Medusa React
|
||||
|
||||
This guide also includes code snippets to send requests to your Medusa backend using Medusa React, among other methods.
|
||||
|
||||
If you follow the Medusa React code blocks, it's assumed you already have [Medusa React installed](../../../medusa-react/overview.mdx) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.mdx#usage).
|
||||
|
||||
### Authenticated Admin User
|
||||
|
||||
You must be an authenticated admin user before following along with the steps in the tutorial.
|
||||
|
||||
You can learn more about [authenticating as an admin user in the API reference](https://docs.medusajs.com/api/admin#authentication).
|
||||
|
||||
---
|
||||
|
||||
## List Publishable API Keys
|
||||
|
||||
You can retrieve a list of publishable API keys by sending a request to the [List Publishable API Keys route](https://docs.medusajs.com/api/admin#publishable-api-keys_getpublishableapikeys):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.publishableApiKeys.list()
|
||||
.then(({ publishable_api_keys, count, limit, offset }) => {
|
||||
console.log(publishable_api_keys)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { PublishableApiKey } from "@medusajs/medusa"
|
||||
import { useAdminPublishableApiKeys } from "medusa-react"
|
||||
|
||||
const PublishableApiKeys = () => {
|
||||
const { publishable_api_keys, isLoading } =
|
||||
useAdminPublishableApiKeys()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{publishable_api_keys && !publishable_api_keys.length && (
|
||||
<span>No Publishable API Keys</span>
|
||||
)}
|
||||
{publishable_api_keys &&
|
||||
publishable_api_keys.length > 0 && (
|
||||
<ul>
|
||||
{publishable_api_keys.map(
|
||||
(publishableApiKey: PublishableApiKey) => (
|
||||
<li key={publishableApiKey.id}>
|
||||
{publishableApiKey.title}
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PublishableApiKeys
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/publishable-api-keys`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ publishable_api_keys, count, limit, offset }) => {
|
||||
console.log(publishable_api_keys)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/publishable-api-keys' \
|
||||
-H 'x-medusa-access-token: <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request does not require any path parameters. You can pass it optional query parameters related to expanding fields and pagination, which you can check out in the [API reference](https://docs.medusajs.com/api/admin#publishable-api-keys_getpublishableapikeys).
|
||||
|
||||
This request returns the following data in the response:
|
||||
|
||||
- `publishable_api_keys`: An array of publishable API keys.
|
||||
- `limit`: The maximum number of keys that can be returned.
|
||||
- `offset`: The number of keys skipped in the result.
|
||||
- `count`: The total number of keys available.
|
||||
|
||||
:::note
|
||||
|
||||
You can learn more about pagination in the [API reference](https://docs.medusajs.com/api/admin#pagination).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Create a Publishable API Key
|
||||
|
||||
You can create a publishable API key by sending a request to the [Create Publishable API Key route](https://docs.medusajs.com/api/admin#publishable-api-keys_postpublishableapikeys):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.publishableApiKeys.create({
|
||||
title: "Web API Key",
|
||||
})
|
||||
.then(({ publishable_api_key }) => {
|
||||
console.log(publishable_api_key.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCreatePublishableApiKey } from "medusa-react"
|
||||
|
||||
const CreatePublishableApiKey = () => {
|
||||
const createKey = useAdminCreatePublishableApiKey()
|
||||
// ...
|
||||
|
||||
const handleCreate = (title: string) => {
|
||||
createKey.mutate({
|
||||
title,
|
||||
}, {
|
||||
onSuccess: ({ publishable_api_key }) => {
|
||||
console.log(publishable_api_key.id)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CreatePublishableApiKey
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/publishable-api-keys`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: "Web API Key",
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ publishable_api_key }) => {
|
||||
console.log(publishable_api_key.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/publishable-api-keys' \
|
||||
-H 'x-medusa-access-token: <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"title": "Web API Key"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires a body parameter `title`, which is the title of the Publishable API Key.
|
||||
|
||||
It returns the created publishable API key in the response.
|
||||
|
||||
---
|
||||
|
||||
## Update a Publishable API Key
|
||||
|
||||
You can update a publishable API key’s details by sending a request to the [Update Publishable API Key route](https://docs.medusajs.com/api/admin#publishable-api-keys_postpublishableapikyspublishableapikey):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.publishableApiKeys.update(publishableApiKeyId, {
|
||||
title: "Web API Key",
|
||||
})
|
||||
.then(({ publishable_api_key }) => {
|
||||
console.log(publishable_api_key.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminUpdatePublishableApiKey } from "medusa-react"
|
||||
|
||||
type Props = {
|
||||
publishableApiKeyId: string
|
||||
}
|
||||
|
||||
const PublishableApiKey = ({
|
||||
publishableApiKeyId
|
||||
}: Props) => {
|
||||
const updateKey = useAdminUpdatePublishableApiKey(
|
||||
publishableApiKeyId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleUpdate = (title: string) => {
|
||||
updateKey.mutate({
|
||||
title,
|
||||
}, {
|
||||
onSuccess: ({ publishable_api_key }) => {
|
||||
console.log(publishable_api_key.id)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default PublishableApiKey
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/publishable-api-keys/${publishableApiKeyId}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: "Web API Key",
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ publishable_api_key }) => {
|
||||
console.log(publishable_api_key.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/publishable-api-keys/<PUBLISHABLE_API_KEY>' \
|
||||
-H 'x-medusa-access-token: <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"title": "Web API Key"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the ID of the publishable API key as a path parameter. In its body, it optionally accepts the new `title` of the publishable API key.
|
||||
|
||||
This request returns the update publishable API key object in the response.
|
||||
|
||||
---
|
||||
|
||||
## Revoke a Publishable API Key
|
||||
|
||||
Revoking a publishable API key does not remove it, but does not allow using it in future requests.
|
||||
|
||||
You can revoke a publishable API key by sending a request to the [Revoke Publishable API Key route](https://docs.medusajs.com/api/admin#publishable-api-keys_postpublishableapikeyspublishableapikeyrevoke):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.publishableApiKeys.revoke(publishableApiKeyId)
|
||||
.then(({ publishable_api_key }) => {
|
||||
console.log(publishable_api_key.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminRevokePublishableApiKey } from "medusa-react"
|
||||
|
||||
type Props = {
|
||||
publishableApiKeyId: string
|
||||
}
|
||||
|
||||
const PublishableApiKey = ({
|
||||
publishableApiKeyId
|
||||
}: Props) => {
|
||||
const revokeKey = useAdminRevokePublishableApiKey(
|
||||
publishableApiKeyId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleRevoke = () => {
|
||||
revokeKey.mutate(void 0, {
|
||||
onSuccess: ({ publishable_api_key }) => {
|
||||
console.log(publishable_api_key.revoked_at)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default PublishableApiKey
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(
|
||||
`<BACKEND_URL>/admin/publishable-api-keys/${publishableApiKeyId}/revoke`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ publishable_api_key }) => {
|
||||
console.log(publishable_api_key.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/publishable-api-keys/<PUBLISHABLE_API_KEY>/revoke' \
|
||||
-H 'x-medusa-access-token: <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the ID of the publishable API key as a path parameter. It returns the updated publishable API key in the response.
|
||||
|
||||
---
|
||||
|
||||
## Delete a Publishable API Key
|
||||
|
||||
You can delete a publishable API key by sending a request to the [Delete Publishable API Key route](https://docs.medusajs.com/api/admin#publishable-api-keys_deletepublishableapikeyspublishableapikey):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.publishableApiKeys.delete(publishableApiKeyId)
|
||||
.then(({ id, object, deleted }) => {
|
||||
console.log(id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDeletePublishableApiKey } from "medusa-react"
|
||||
|
||||
type Props = {
|
||||
publishableApiKeyId: string
|
||||
}
|
||||
|
||||
const PublishableApiKey = ({
|
||||
publishableApiKeyId
|
||||
}: Props) => {
|
||||
const deleteKey = useAdminDeletePublishableApiKey(
|
||||
publishableApiKeyId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleDelete = () => {
|
||||
deleteKey.mutate(void 0, {
|
||||
onSuccess: ({ id, object, deleted }) => {
|
||||
console.log(id)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default PublishableApiKey
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/publishable-api-keys/${publishableApiKeyId}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ id, object, deleted }) => {
|
||||
console.log(id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X DELETE '<BACKEND_URL>/admin/publishable-api-keys/<PUBLISHABLE_API_KEY>' \
|
||||
-H 'x-medusa-access-token: <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the ID of the publishable API key as a path parameter.
|
||||
|
||||
It returns the following data in the response:
|
||||
|
||||
- `id`: The ID of the deleted publishable API key.
|
||||
- `object`: A string indicating the type of object deleted. By default, its value is `publishable_api_key`.
|
||||
- `deleted`: A boolean value indicating whether the publishable API key was deleted or not.
|
||||
|
||||
---
|
||||
|
||||
## Manage Sales Channels of Publishable API Keys
|
||||
|
||||
This section covers how to manage sales channels in a publishable API key. This doesn’t affect sales channels and their data, only its association with the publishable API key.
|
||||
|
||||
### List Sales Channels of a Publishable API Key
|
||||
|
||||
You can retrieve the list of sales channels associated with a publishable API key by sending a request to the [List Sales Channels API Route](https://docs.medusajs.com/api/admin#sales-channels_getsaleschannels):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.publishableApiKeys
|
||||
.listSalesChannels(publishableApiKeyId)
|
||||
.then(({ sales_channels }) => {
|
||||
console.log(sales_channels)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import {
|
||||
useAdminPublishableApiKeySalesChannels,
|
||||
} from "medusa-react"
|
||||
|
||||
type Props = {
|
||||
publishableApiKeyId: string
|
||||
}
|
||||
|
||||
const SalesChannels = ({
|
||||
publishableApiKeyId
|
||||
}: Props) => {
|
||||
const { sales_channels, isLoading } =
|
||||
useAdminPublishableApiKeySalesChannels(
|
||||
publishableApiKeyId
|
||||
)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{sales_channels && !sales_channels.length && (
|
||||
<span>No Sales Channels</span>
|
||||
)}
|
||||
{sales_channels && sales_channels.length > 0 && (
|
||||
<ul>
|
||||
{sales_channels.map((salesChannel) => (
|
||||
<li key={salesChannel.id}>{salesChannel.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SalesChannels
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(
|
||||
`<BACKEND_URL>/admin/publishable-api-keys/${publishableApiKeyId}/sales-channels`,
|
||||
{
|
||||
credentials: "include",
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ sales_channels }) => {
|
||||
console.log(sales_channels)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/publishable-api-keys/<PUBLISHABLE_API_KEY>/sales-channels' \
|
||||
-H 'x-medusa-access-token: <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the ID of the publishable API key as a path parameter.
|
||||
|
||||
It returns an array of sales channels associated with the publishable API key in the response.
|
||||
|
||||
### Add Sales Channels to Publishable API Key
|
||||
|
||||
You can add a sales channel to a publishable API key by sending a request to the [Add Sales Channels API Route](https://docs.medusajs.com/api/admin#publishable-api-keys_postpublishableapikeysaleschannelschannelsbatch):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.publishableApiKeys.addSalesChannelsBatch(
|
||||
publishableApiKeyId,
|
||||
{
|
||||
sales_channel_ids: [
|
||||
{
|
||||
id: salesChannelId,
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
.then(({ publishable_api_key }) => {
|
||||
console.log(publishable_api_key.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import {
|
||||
useAdminAddPublishableKeySalesChannelsBatch,
|
||||
} from "medusa-react"
|
||||
|
||||
type Props = {
|
||||
publishableApiKeyId: string
|
||||
}
|
||||
|
||||
const PublishableApiKey = ({
|
||||
publishableApiKeyId
|
||||
}: Props) => {
|
||||
const addSalesChannels =
|
||||
useAdminAddPublishableKeySalesChannelsBatch(
|
||||
publishableApiKeyId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleAdd = (salesChannelId: string) => {
|
||||
addSalesChannels.mutate({
|
||||
sales_channel_ids: [
|
||||
{
|
||||
id: salesChannelId,
|
||||
},
|
||||
],
|
||||
}, {
|
||||
onSuccess: ({ publishable_api_key }) => {
|
||||
console.log(publishable_api_key.id)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default PublishableApiKey
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(
|
||||
`<BACKEND_URL>/admin/publishable-api-keys/${publishableApiKeyId}/sales-channels/batch`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
sales_channel_ids: [
|
||||
{
|
||||
id: salesChannelId,
|
||||
},
|
||||
],
|
||||
}),
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ publishable_api_key }) => {
|
||||
console.log(publishable_api_key.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/publishable-api-keys/<PUBLISHABLE_API_KEY>/sales-channels/batch' \
|
||||
-H 'x-medusa-access-token: <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"sales_channel_ids": [
|
||||
{
|
||||
"id": "<SALES_CHANNEL_ID>"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires passing the ID of the publishable API key as a path parameter.
|
||||
|
||||
In its body parameters, it’s required to pass the `sales_channel_ids` array parameter. Each item in the array is an object containing an `id` property, with its value being the ID of the sales channel to add to the publishable API key.
|
||||
|
||||
You can add more than one sales channel in the same request by passing each of them as an object in the array.
|
||||
|
||||
This request returns the updated publishable API key in the response.
|
||||
|
||||
### Delete Sales Channels from a Publishable API Key
|
||||
|
||||
You can delete a sales channel from a publishable API key by sending a request to the [Delete Sales Channels API Route](https://docs.medusajs.com/api/admin#publishable-api-keys_deletepublishableapikeysaleschannelschannelsbatch):
|
||||
|
||||
<Tabs groupId="request-type" isCodeTabs={true}>
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.publishableApiKeys.deleteSalesChannelsBatch(
|
||||
publishableApiKeyId,
|
||||
{
|
||||
sales_channel_ids: [
|
||||
{
|
||||
id: salesChannelId,
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
.then(({ publishable_api_key }) => {
|
||||
console.log(publishable_api_key.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import {
|
||||
useAdminRemovePublishableKeySalesChannelsBatch,
|
||||
} from "medusa-react"
|
||||
|
||||
type Props = {
|
||||
publishableApiKeyId: string
|
||||
}
|
||||
|
||||
const PublishableApiKey = ({
|
||||
publishableApiKeyId
|
||||
}: Props) => {
|
||||
const deleteSalesChannels =
|
||||
useAdminRemovePublishableKeySalesChannelsBatch(
|
||||
publishableApiKeyId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleDelete = (salesChannelId: string) => {
|
||||
deleteSalesChannels.mutate({
|
||||
sales_channel_ids: [
|
||||
{
|
||||
id: salesChannelId,
|
||||
},
|
||||
],
|
||||
}, {
|
||||
onSuccess: ({ publishable_api_key }) => {
|
||||
console.log(publishable_api_key.id)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default PublishableApiKey
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(
|
||||
`<BACKEND_URL>/admin/publishable-api-keys/${publishableApiKeyId}/sales-channels/batch`,
|
||||
{
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
sales_channel_ids: [
|
||||
{
|
||||
id: salesChannelId,
|
||||
},
|
||||
],
|
||||
}),
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then(({ publishable_api_key }) => {
|
||||
console.log(publishable_api_key.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X DELETE '<BACKEND_URL>/admin/publishable-api-keys/<PUBLISHABLE_API_KEY>/sales-channels/batch' \
|
||||
-H 'x-medusa-access-token: <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"sales_channel_ids": [
|
||||
{
|
||||
"id": "<SALES_CHANNEL_ID>"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the ID of the publishable API key as a path parameter.
|
||||
|
||||
In its body parameters, it’s required to pass the `sales_channel_ids` array parameter. Each item in the array is an object containing an `id` property, with its value being the ID of the sales channel to delete from the publishable API key.
|
||||
|
||||
You can delete more than one sales channel in the same request by passing each of them as an object in the array.
|
||||
|
||||
This request returns the updated publishable API key in the response.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Use publishable API keys in client requests](../storefront/use-in-requests.md)
|
||||
@@ -1,69 +0,0 @@
|
||||
---
|
||||
description: 'Learn what publishable API keys are and how they can be used in the Medusa backend. Publishable API keys can be used to scope API calls with an API key.'
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Publishable API Keys
|
||||
|
||||
In this document, you’ll learn about Publishable API Keys and their architecture.
|
||||
|
||||
## Introduction
|
||||
|
||||
While using Medusa’s APIs, you might have to pass some query parameters for certain resources with every or most requests.
|
||||
|
||||
Taking Sales Channels as an example, you have to pass the Sales Channel’s ID as a query parameter to all the necessary API Routes, such as the List Products API Route.
|
||||
|
||||
This is a tedious and error-prone process. This is where Publishable API Keys are useful.
|
||||
|
||||
Publishable API Keys can be used to scope API calls with an API key, determining what resources are retrieved when querying the API. Currently, they can be associated only with Sales Channels.
|
||||
|
||||
For example, you can associate an API key with a B2B channel, then, on the storefront, retrieve only products available in that channel using the API key.
|
||||
|
||||
---
|
||||
|
||||
## PublishableApiKey Entity Overview
|
||||
|
||||
The `PublishableApiKey` entity represents a publishable API key that is stored in the database. Some of its important attributes include:
|
||||
|
||||
- `id`: The ID of the publishable API key. This is the API key you’ll use in your API requests.
|
||||
- `created_by`: The ID of the user that created this API key.
|
||||
- `revoked_by`: The ID of the user that revoked this API key. A revoked publishable API key cannot be used in requests.
|
||||
|
||||
---
|
||||
|
||||
## Relation to Other Entities
|
||||
|
||||
### Sales Channels
|
||||
|
||||
A publishable API key can be associated with more than one sales channel, and a sales channel can be associated with more than one publishable API key.
|
||||
|
||||
The relation is represented by the entity `PublishableApiKeySalesChannel`.
|
||||
|
||||
---
|
||||
|
||||
## Custom Development
|
||||
|
||||
Developers can manage Publishable API Keys and use them when making requests to the Store APIs.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/publishable-api-keys/admin/manage-publishable-api-keys',
|
||||
label: 'Admin: Manage Publishable API Keys',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage publishable API keys using Admin APIs.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/publishable-api-keys/storefront/use-in-requests',
|
||||
label: 'Storefront: Use in Requests',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to use publishable API keys in a storefront.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
@@ -1,88 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to use Publishable API Keys in Client Requests using Medusa JS Client, Medusa React, or other methods.'
|
||||
---
|
||||
|
||||
# Use Publishable API Keys in Client Requests
|
||||
|
||||
In this document, you'll learn how to use Publishable API Keys in client requests.
|
||||
|
||||
:::note
|
||||
|
||||
[Publishable API keys](../index.mdx) are only for client-side use. They can be publicly accessible in your code, as they are not authorized for the Admin API.
|
||||
|
||||
:::
|
||||
|
||||
## Default Behaviour In Product Store API Routes
|
||||
|
||||
If you don't pass a publishable API Key for the store API Routes `/store/products` and `/store/products/{product_id}`, the default sales channel of the store is assigned to the request.
|
||||
|
||||
---
|
||||
|
||||
## Using Medusa JS Client
|
||||
|
||||
When using [Medusa’s JS Client](../../../js-client/overview.mdx), you can pass it to the client only once when you create the instance of the client:
|
||||
|
||||
```ts
|
||||
const medusa = new Medusa({
|
||||
maxRetries: 3,
|
||||
baseUrl: "https://api.example.com",
|
||||
publishableApiKey,
|
||||
})
|
||||
```
|
||||
|
||||
This will add the API key as in the header parameter `x-publishable-api-key` on all requests.
|
||||
|
||||
You can also use the `setPublishableKey` method to set it at a later point:
|
||||
|
||||
```ts
|
||||
const medusa = new Medusa({
|
||||
// ...
|
||||
})
|
||||
|
||||
// at a later point
|
||||
medusa.setPublishableKey(publishableApiKey)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Using Medusa React
|
||||
|
||||
You can pass the publishable API key to the `MedusaProvider` component:
|
||||
|
||||
```tsx
|
||||
const App = () => {
|
||||
return (
|
||||
<MedusaProvider
|
||||
queryClientProviderProps={{ client: queryClient }}
|
||||
baseUrl="http://localhost:9000"
|
||||
// ...
|
||||
publishableApiKey={publishableApiKey}
|
||||
>
|
||||
<MyStorefront />
|
||||
</MedusaProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Then, the API key will be passed in the header parameter `x-publishable-api-key` of every request.
|
||||
|
||||
---
|
||||
|
||||
## Using Other Methods
|
||||
|
||||
For other ways of sending requests to your Medusa backend, such as using the Fetch API, you must pass `x-publishable-api-key` in the header of every request. Its value is the publishable API key’s `id`.
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/products`, {
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"x-publishable-api-key": publishableApiKey,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Manage publishable keys as an admin](../admin/manage-publishable-api-keys.mdx)
|
||||
@@ -1,186 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to create a scheduled job in Medusa. The scheduled job in this example will simply change the status of draft products to published.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# (Deprecated) How to Create a Scheduled Job
|
||||
|
||||
In this document, you’ll learn how to create a scheduled job in Medusa.
|
||||
|
||||
:::note
|
||||
|
||||
Following v1.18 of `@medusajs/medusa`, the approach in this guide is deprecated. It's recommended to follow [this guide](./create.md) instead.
|
||||
|
||||
:::
|
||||
|
||||
## Overview
|
||||
|
||||
Medusa allows you to create scheduled jobs that run at specific times during your backend's lifetime. For example, you can synchronize your inventory with an Enterprise Resource Planning (ERP) system once a day.
|
||||
|
||||
This guide explains how to create a scheduled job on your Medusa backend. The scheduled job in this example will simply change the status of draft products to `published`.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Components
|
||||
|
||||
It is assumed that you already have a Medusa backend installed and set up. If not, you can follow the [quickstart guide](../backend/install.mdx) to get started. The Medusa backend must also have an event bus module installed, which is available when using the default Medusa backend starter.
|
||||
|
||||
---
|
||||
|
||||
## 1. Create a File
|
||||
|
||||
Each scheduled job should reside in a [loader](../loaders/overview.mdx), which is a TypeScript or JavaScript file located under the `src/loaders` directory.
|
||||
|
||||
Start by creating the `src/loaders` directory. Then, inside that directory, create the JavaScript or TypeScript file that you’ll add the scheduled job in. You can use any name for the file.
|
||||
|
||||
For the example in this tutorial, you can create the file `src/loaders/publish.ts`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Create Scheduled Job
|
||||
|
||||
To create a scheduled job, add the following code in the file you created, which is `src/loaders/publish.ts` in this example:
|
||||
|
||||
```ts title="src/loaders/publish.ts"
|
||||
import { MedusaContainer } from "@medusajs/medusa"
|
||||
|
||||
const publishJob = async (
|
||||
container: MedusaContainer,
|
||||
options: Record<string, any>
|
||||
) => {
|
||||
const jobSchedulerService =
|
||||
container.resolve("jobSchedulerService")
|
||||
jobSchedulerService.create(
|
||||
"publish-products",
|
||||
{},
|
||||
"0 0 * * *",
|
||||
async () => {
|
||||
// job to execute
|
||||
const productService = container.resolve("productService")
|
||||
const draftProducts = await productService.list({
|
||||
status: "draft",
|
||||
})
|
||||
|
||||
for (const product of draftProducts) {
|
||||
await productService.update(product.id, {
|
||||
status: "published",
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export default publishJob
|
||||
```
|
||||
|
||||
:::info
|
||||
|
||||
The service taking care of background jobs was renamed in v1.7.1. If you are running a previous version, use `eventBusService` instead of `jobSchedulerService`.
|
||||
|
||||
:::
|
||||
|
||||
This file should export a function that accepts a `container` and `options` parameters. `container` is the dependency container that you can use to resolve services, such as the JobSchedulerService. `options` are the plugin’s options if this scheduled job is created in a plugin.
|
||||
|
||||
You then resolve the `JobSchedulerService` and use the `jobSchedulerService.create` method to create the scheduled job. This method accepts four parameters:
|
||||
|
||||
- The first parameter is a unique name to give to the scheduled job. In the example above, you use the name `publish-products`.
|
||||
- The second parameter is an object which can be used to [pass data to the job](#pass-data-to-the-scheduled-job).
|
||||
- The third parameter is the scheduled job expression pattern. In this example, it will execute the scheduled job once a day at 12 AM.
|
||||
- The fourth parameter is the function to execute. This is where you add the code to execute once the scheduled job runs. In this example, you retrieve the draft products using the [ProductService](../../references/services/classes/services.ProductService.mdx) and update the status of each of these products to `published`.
|
||||
|
||||
:::tip
|
||||
|
||||
You can see examples of scheduled job expression patterns on [crontab guru](https://crontab.guru/examples.html).
|
||||
|
||||
:::
|
||||
|
||||
### Scheduled Job Name
|
||||
|
||||
As mentioned earlier, the first parameter of the `create` method is the name of the scheduled job. By default, if another scheduled job has the same name, your custom scheduled job will replace it.
|
||||
|
||||
If you want to ensure both scheduled jobs are registered and used, you can pass as a fifth parameter an options object with a `keepExisting` property set to `true`. For example:
|
||||
|
||||
```ts
|
||||
jobSchedulerService.create(
|
||||
"publish-products",
|
||||
{},
|
||||
"0 0 * * *",
|
||||
async () => {
|
||||
// ...
|
||||
},
|
||||
{
|
||||
keepExisting: true,
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Pass Data to the Scheduled Job
|
||||
|
||||
To pass data to your scheduled job, add the data to the object passed as a second parameter under the `data` property. This is helpful if you use one function to handle multiple scheduled jobs.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
jobSchedulerService.create("publish-products", {
|
||||
data: {
|
||||
productId,
|
||||
},
|
||||
}, "0 0 * * *", async (job) => {
|
||||
console.log(job.data) // {productId: 'prod_124...'}
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Run Medusa Backend
|
||||
|
||||
:::info
|
||||
|
||||
Scheduled Jobs only run while the Medusa backend is running.
|
||||
|
||||
:::
|
||||
|
||||
Before you run the Medusa backend, make sure to build your code that's under the `src` directory into the `dist` directory with the following command:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
Then, run the following command to start your Medusa backend:
|
||||
|
||||
```bash npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
If the scheduled job was registered successfully, you should see a message similar to this logged on your Medusa backend:
|
||||
|
||||
```bash
|
||||
Registering publish-products
|
||||
```
|
||||
|
||||
Where `publish-products` is the unique name you provided to the scheduled job.
|
||||
|
||||
Once it is time to run your scheduled job based on the scheduled job expression pattern, the scheduled job will run and you can see it logged on your Medusa backend.
|
||||
|
||||
For example, the above scheduled job will run at 12 AM and, when it runs, you can see the following logged on your Medusa backend:
|
||||
|
||||
```bash noReport
|
||||
info: Processing scheduled job: publish-products
|
||||
```
|
||||
|
||||
If you log anything in the scheduled job, for example using `console.log`, or if any errors are thrown, it’ll also be logged on your Medusa backend.
|
||||
|
||||
:::tip
|
||||
|
||||
To test the previous example out instantly, you can change the scheduled job expression pattern passed as the third parameter to `jobSchedulerService.create` to `* * * * *`. This will run the scheduled job every minute.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Create a Plugin](../plugins/create.mdx)
|
||||
@@ -1,146 +0,0 @@
|
||||
---
|
||||
description: 'Learn how to create a scheduled job in Medusa. The scheduled job in this example will simply change the status of draft products to published.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Create a Scheduled Job
|
||||
|
||||
In this document, you’ll learn how to create a scheduled job in Medusa.
|
||||
|
||||
:::tip
|
||||
|
||||
v1.18 of `@medusajs/medusa` introduced a new approach to create a scheduled job. If you're looking for the old guide, you can find it [here](./create-deprecated.md). However, it's highly recommended you follow this new approach, as the old one is deprecated.
|
||||
|
||||
:::
|
||||
|
||||
## Overview
|
||||
|
||||
Medusa allows you to create scheduled jobs that run at specific times during your backend's lifetime. For example, you can synchronize your inventory with an Enterprise Resource Planning (ERP) system once a day.
|
||||
|
||||
This guide explains how to create a scheduled job on your Medusa backend. The scheduled job in this example will simply change the status of draft products to `published`.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To use scheduled jobs, you must configure Redis in your Medusa backend. Learn more in the [Configurations documentation](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx#redis_url).
|
||||
|
||||
---
|
||||
|
||||
## Create Scheduled Job
|
||||
|
||||
Scheduled jobs are TypeScript or JavaScript guides placed under the `src/jobs` directory. It can be created under subdirectories of `src/jobs` as well.
|
||||
|
||||
The scheduled job file exports a default handler function, and the scheduled job's configurations.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/jobs/publish.ts"
|
||||
import {
|
||||
type ProductService,
|
||||
type ScheduledJobConfig,
|
||||
type ScheduledJobArgs,
|
||||
ProductStatus,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export default async function handler({
|
||||
container,
|
||||
data,
|
||||
pluginOptions,
|
||||
}: ScheduledJobArgs) {
|
||||
const productService: ProductService = container.resolve(
|
||||
"productService"
|
||||
)
|
||||
const draftProducts = await productService.list({
|
||||
status: ProductStatus.DRAFT,
|
||||
})
|
||||
|
||||
for (const product of draftProducts) {
|
||||
await productService.update(product.id, {
|
||||
status: ProductStatus.PUBLISHED,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const config: ScheduledJobConfig = {
|
||||
name: "publish-once-a-day",
|
||||
schedule: "0 0 * * *",
|
||||
data: {},
|
||||
}
|
||||
```
|
||||
|
||||
### Scheduled Job Configuration
|
||||
|
||||
The exported configuration object of type `ScheduledJobConfig` must include the following properties:
|
||||
|
||||
- `name`: a string indicating a unique name to give to the scheduled job. If another scheduled job has the same name, your custom scheduled job will replace it.
|
||||
- `schedule`: a string indicating a [cron schedule](https://crontab.guru/#0_0_*_*_*) that determines when the job should run. In this example, it will execute the scheduled job once a day at 12 AM.
|
||||
- `data`: Optional data you want to pass to the job.
|
||||
|
||||
:::tip
|
||||
|
||||
You can see examples of scheduled job expression patterns on [crontab guru](https://crontab.guru/examples.html).
|
||||
|
||||
:::
|
||||
|
||||
### Scheduled Job Handler Function
|
||||
|
||||
The default-export of the scheduled job file is a handler function that is executed when the events specified in the exported configuration is triggered.
|
||||
|
||||
The function accepts a parameter of type `ScheduledJobArgs`, which has the following properties:
|
||||
|
||||
- `container`: The [dependency container](../fundamentals/dependency-injection.md) that allows you to resolve Medusa resources, such as services.
|
||||
- `data`: The data passed in the [configuration object](#scheduled-job-configuration).
|
||||
- `pluginOptions`: When the scheduled job is created within a plugin, this object holds the plugin's options defined in the [Medusa configurations](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx).
|
||||
|
||||
---
|
||||
|
||||
## Test it Out
|
||||
|
||||
:::info
|
||||
|
||||
Scheduled Jobs only run while the Medusa backend is running.
|
||||
|
||||
:::
|
||||
|
||||
Before you run the Medusa backend, make sure to build your code that's under the `src` directory into the `dist` directory with the following command:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
Then, run the following command to start your Medusa backend:
|
||||
|
||||
```bash npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
If the scheduled job is registered successfully, you should see a message similar to this logged on your Medusa backend:
|
||||
|
||||
```bash
|
||||
Registering publish-once-a-day
|
||||
```
|
||||
|
||||
Where `publish-once-a-day` is the unique name you provided to the scheduled job.
|
||||
|
||||
Once it's time to run your scheduled job based on the `schedule` configuration, the scheduled job will run and you can see it logged on your Medusa backend.
|
||||
|
||||
For example, the above scheduled job will run at 12 AM and, when it runs, you can see the following logged on your Medusa backend:
|
||||
|
||||
```bash noReport
|
||||
info: Processing scheduled job: publish-once-a-day
|
||||
```
|
||||
|
||||
If you log anything in the scheduled job, for example using `console.log`, or if any errors are thrown, it’ll also be logged on your Medusa backend.
|
||||
|
||||
:::tip
|
||||
|
||||
To test the previous example out instantly, you can change the value of `schedule` in the exported configuration object to `* * * * *`. This will run the scheduled job every minute.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Create a Plugin](../plugins/create.mdx)
|
||||
@@ -1,40 +0,0 @@
|
||||
---
|
||||
description: "Learn what scheduled jobs are in Medusa. Scheduled jobs (also known as cron jobs) are tasks performed at a specific time in the Medusa Backend."
|
||||
---
|
||||
|
||||
import DocCard from '@theme/DocCard';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Scheduled Jobs
|
||||
|
||||
In this document, you’ll learn what scheduled jobs are in Medusa.
|
||||
|
||||
## Introduction
|
||||
|
||||
Scheduled jobs (also known as cron jobs) are tasks performed at a specific time while the Medusa Backend is running. They’re used to perform asynchronous tasks in the background.
|
||||
|
||||
For example, you can synchronize your inventory with an Enterprise Resource Planning (ERP) system once a day using a scheduled job.
|
||||
|
||||
In the Medusa Backend, the scheduled jobs queue is implemented using [Redis](https://redis.io/) and [Bull](https://www.npmjs.com/package/bull). So, for scheduled jobs to work, you must have [Redis installed and enabled](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx#redis_url).
|
||||
|
||||
:::tip
|
||||
|
||||
Future versions of Medusa will allow switching out Redis and using a different pub/sub service.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Custom Development
|
||||
|
||||
Developers can create an unlimited number of scheduled jobs within the Medusa Backend, a plugin, or a custom module.
|
||||
|
||||
<DocCard item={{
|
||||
type: 'link',
|
||||
href: '/development/scheduled-jobs/create',
|
||||
label: 'Create a Scheduled Job',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a scheduled job.'
|
||||
}
|
||||
}} />
|
||||
@@ -1,44 +0,0 @@
|
||||
---
|
||||
description: "Learn what a search service is and how it’s used in Medusa. A search service is used to manage search indices of searchable items, such as products, and providing results for search operations."
|
||||
---
|
||||
|
||||
import DocCard from '@theme/DocCard';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Search Service
|
||||
|
||||
In this document, you’ll learn what a search service is and how it’s used in Medusa.
|
||||
|
||||
## Overview
|
||||
|
||||
A search service is used to manage search indices of searchable items, such as products, and providing results for search operations. Although the Medusa core provides basic search functionalities through its API Routes, a search service allows you to integrate third-party services for an optimized search experience and rich search functionalities.
|
||||
|
||||
A search service is a service class that is defined in a TypeScript or JavaScript file, which is created in the `src/services` directory of your Medusa backend codebase or plugin. The class must extend the `AbstractSearchService` class imported from the `@medusajs/utils` package.
|
||||
|
||||
Using the [dependency container and injection](../fundamentals/dependency-injection.md), the Medusa backend will then use and resolve the search service within the backend’s search operations, such as when the [Search Products API Route](https://docs.medusajs.com/api/store#products_postproductssearch) is used. You can also [resolve the service](../services/create-service.mdx#use-a-service) within your resources to trigger the search where necessary.
|
||||
|
||||
Medusa provides official plugins that you can install and use in your Medusa backend. Check out available search plugins [here](../../plugins/search/index.mdx).
|
||||
|
||||
### Hierarchy of Search Services
|
||||
|
||||
Medusa provides a default search service that doesn’t actually perform any indexing or searching, but acts like a placeholder search service. Only one search service is registered in the dependency container under the `searchService` name.
|
||||
|
||||
If you install a search plugin, the search service within it will be registered in the dependency container and used throughout the Medusa backend.
|
||||
|
||||
If you create your own search service in the Medusa backend codebase, it will be registered in the dependency container and used throughout the Medusa backend.
|
||||
|
||||
---
|
||||
|
||||
## Custom Development
|
||||
|
||||
Developers can create a custom search service with the desired functionality or third-party integration either directly within the Medusa Core, in a plugin, or in a module.
|
||||
|
||||
<DocCard item={{
|
||||
type: 'link',
|
||||
href: '/development/search/create',
|
||||
label: 'Create a Search service',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a search service.'
|
||||
}
|
||||
}} />
|
||||
@@ -1,690 +0,0 @@
|
||||
---
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import DetailsList from '@site/src/components/DetailsList'
|
||||
import ServiceLifetimeSection from '../../troubleshooting/awilix-resolution-error/_service-lifetime.md'
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Create a Service
|
||||
|
||||
In this document, you’ll learn how you can create a [Service](./overview.mdx) and use it across your Medusa backend just like any of the core services.
|
||||
|
||||
## Basic Service Implementation
|
||||
|
||||
To create a service, create a TypeScript or JavaScript file in `src/services` to hold the service. The name of the file should be the name of the service without `Service`. This is essential as the file name is used when registering the service in the [dependency container](../fundamentals/dependency-injection.md), and `Service` is appended to the camel-case version of the file name automatically.
|
||||
|
||||
For example, if you want to create a service `PostService`, eventually registered as `postService`, create the file `post.ts` in `src/services` with the following content:
|
||||
|
||||
```ts title="src/services/post.ts"
|
||||
import { TransactionBaseService } from "@medusajs/medusa"
|
||||
|
||||
class PostService extends TransactionBaseService {
|
||||
getMessage() {
|
||||
return `Welcome to My Store!`
|
||||
}
|
||||
}
|
||||
|
||||
export default PostService
|
||||
```
|
||||
|
||||
This service will be registered in the [dependency container](../fundamentals/dependency-injection.md) as `postService`. It contains a single sample method `getMessage`.
|
||||
|
||||
---
|
||||
|
||||
## Build Files
|
||||
|
||||
Custom services must be transpiled and moved to the `dist` directory before you can start consuming them. When you run your backend using either the `medusa develop` or `npx medusa develop` commands, it watches the files under `src` for any changes, then triggers the `build` command and restarts the server.
|
||||
|
||||
However, the build isn't triggered when the backend first starts running, and it's never triggered when the `medusa start` or `npx medusa start` commands are used.
|
||||
|
||||
So, make sure to run the `build` command before starting the backend:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Service Constructor
|
||||
|
||||
As the service extends the `TransactionBaseService` class, all resources registered in the dependency container can be accessed through [dependency injection](../fundamentals/dependency-injection.md). This includes services, repositories, and other resources in the Medusa core, as well as your custom services and resources.
|
||||
|
||||
So, if you want your service to use another service, add it as part of your constructor’s dependencies and set it to a field inside your service’s class:
|
||||
|
||||
```ts title="src/services/post.ts"
|
||||
import { ProductService } from "@medusajs/medusa"
|
||||
import { PostRepository } from "../repositories/post"
|
||||
|
||||
class PostService extends TransactionBaseService {
|
||||
private productService: ProductService
|
||||
|
||||
constructor(container) {
|
||||
super(container)
|
||||
this.productService = container.productService
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Then, you can use that service anywhere in your custom service. For example:
|
||||
|
||||
```ts title="src/services/post.ts"
|
||||
class PostService extends TransactionBaseService {
|
||||
// ...
|
||||
async getProductCount() {
|
||||
return await this.productService.count()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use Repositories
|
||||
|
||||
As your service provides helper methods related to one or more entities in your backend, you'll need to perform operations on that entity. To do that, you need to use the entity's repository.
|
||||
|
||||
Repositories, just like services, are registered in the dependency container and can be accessed with [dependency injection](../fundamentals/dependency-injection.md).
|
||||
|
||||
However, to actually get an instance of the repository within the service's methods, you need to use the service's `activeManager_`, which is declared in the parent class `TransactionBaseService`. `activeManager_` is an instance of Typeorm's `EntityManager` and has a method `withRepository` which allows you to retrieve an instance of the repository.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/services/post.ts"
|
||||
import { PostRepository } from "../repositories/post"
|
||||
|
||||
class PostService extends TransactionBaseService {
|
||||
// ...
|
||||
protected postRepository_: typeof PostRepository
|
||||
|
||||
constructor(container) {
|
||||
super(container)
|
||||
// ...
|
||||
this.postRepository_ = container.postRepository
|
||||
}
|
||||
|
||||
async list(): Promise<Post[]> {
|
||||
const postRepo = this.activeManager_.withRepository(
|
||||
this.postRepository_
|
||||
)
|
||||
return await postRepo.find()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Refer to the [repositories](../entities/repositories.md) documentation to learn about its different methods.
|
||||
|
||||
---
|
||||
|
||||
## Transactions
|
||||
|
||||
Transactions ensure that when an error occurs, all data manipulation within the transaction is reverted. As services are likely to include methods that manipulate data, such as create or update a post, it's very useful to wrap that logic within a transaction block.
|
||||
|
||||
Since services extend the `TransactionBaseService` class, you can use its `atomicPhase_` method. The `atomicPhase_` method allows you to wrap code within a transactional block.
|
||||
|
||||
It accepts as a parameter a function, which includes the logic to be performed inside the transactional block. The function accepts as a parameter a transaction manager, which is Typeorm's `EntityManager`. You can use it within the function to retrieve repositories, among other functionalities.
|
||||
|
||||
The data returned by the function passed as a parameter to the `atomicPhase_` method will be return by the `atomicPhase_` method as well.
|
||||
|
||||
For example, the `PostService`'s `create` method with the `atomicPhase_` method:
|
||||
|
||||
```ts title="src/services/post.ts"
|
||||
class PostService extends TransactionBaseService {
|
||||
protected postRepository_: typeof PostRepository
|
||||
// ...
|
||||
async create(
|
||||
data: Pick<Post, "title" | "author_id">
|
||||
): Promise<Post> {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const postRepo = manager.withRepository(
|
||||
this.postRepository_
|
||||
)
|
||||
const post = postRepo.create()
|
||||
|
||||
post.title = data.title
|
||||
post.author_id = data.author_id
|
||||
|
||||
const result = await postRepo.save(post)
|
||||
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Service Life Time
|
||||
|
||||
As the dependency container in Medusa is built on top of [awilix](https://github.com/jeffijoe/awilix), you can specify the [Lifetime](https://github.com/jeffijoe/awilix#lifetime-management) of a service. The lifetime is added as a static property to the service.
|
||||
|
||||
There are three lifetime types:
|
||||
|
||||
1. `Lifetime.TRANSIENT`: when used, a new instance of the service is created every time it is resolved in other resources from the dependency container.
|
||||
2. `Lifetime.SCOPED`: (default for custom services) when used, an instance of the service is created and reused in the scope of the dependency container. So, when the service is resolved in other resources that share that dependency container, the same instance of the service will be returned.
|
||||
3. `Lifetime.SINGLETON`: (default for core services) when used, the service is always reused, regardless of the scope. An instance of the service is cached in the root container.
|
||||
|
||||
You can set the lifetime of your service by setting the `LIFE_TIME` static property:
|
||||
|
||||
```ts title="src/services/post.ts"
|
||||
import { TransactionBaseService } from "@medusajs/medusa"
|
||||
import { Lifetime } from "awilix"
|
||||
|
||||
class PostService extends TransactionBaseService {
|
||||
static LIFE_TIME = Lifetime.SCOPED
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Medusa Configurations
|
||||
|
||||
Within your service, you may need to access the Medusa configuration exported from `medusa-config.js`. To do that, you can access `configModule` using dependency injection.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/services/post.ts"
|
||||
import {
|
||||
ConfigModule,
|
||||
TransactionBaseService,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
class PostService extends TransactionBaseService {
|
||||
protected readonly configModule_: ConfigModule
|
||||
|
||||
constructor(container) {
|
||||
super(container)
|
||||
// ...
|
||||
this.configModule_ = container.configModule
|
||||
}
|
||||
|
||||
getConfigurations() {
|
||||
return this.configModule_
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default PostService
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pagination, Filtering, and Relations
|
||||
|
||||
Often, your service will provide methods that retrieve a list of items, which can be used by API Routes. In these methods, it can be helpful to provide filtering and pagination utilities that can be used by API Routes or any other resources utilizing this service.
|
||||
|
||||
The `@medusajs/medusa` package provides the following generic types that you can use to create the signature of your method that accepts filtering and pagination parameters:
|
||||
|
||||
1. `Selector`: Can be used to accepts the attributes of an entity as possible filtering parameters, based on each attribute's type.
|
||||
2. `FindConfig`: Can be used to provide pagination parameters such as `skip`, `take`, and `relations`. `skip` indicates how many items to skip before retrieving the results, `take` indicates how many results to return, and `relations`, indicate which relations to expand and include in the returned objects.
|
||||
|
||||
The `@medusajs/medusa` package also provides a `buildQuery` method that allows you to pass two parameter, the first of type `Selector` and the second of type `FindConfig`, to build the object that should be passed to the repository.
|
||||
|
||||
So, for example, to create a method that retrieves a list of posts and the total number of posts available:
|
||||
|
||||
```ts title="src/services/post.ts"
|
||||
import {
|
||||
FindConfig,
|
||||
Selector,
|
||||
TransactionBaseService,
|
||||
buildQuery,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
class PostService extends TransactionBaseService {
|
||||
// ...
|
||||
async listAndCount(
|
||||
selector?: Selector<Post>,
|
||||
config: FindConfig<Post> = {
|
||||
skip: 0,
|
||||
take: 20,
|
||||
relations: [],
|
||||
}): Promise<[Post[], number]> {
|
||||
const postRepo = this.activeManager_.withRepository(
|
||||
this.postRepository_
|
||||
)
|
||||
|
||||
const query = buildQuery(selector, config)
|
||||
|
||||
return postRepo.findAndCount(query)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In addition, you can expand relations when retrieving a single item with the help of `FindConfig` and `buildQuery`.
|
||||
|
||||
For example, to create a method that retrieves a single post:
|
||||
|
||||
```ts title="src/services/post.ts"
|
||||
import {
|
||||
FindConfig,
|
||||
TransactionBaseService,
|
||||
buildQuery,
|
||||
} from "@medusajs/medusa"
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
|
||||
class PostService extends TransactionBaseService {
|
||||
// ...
|
||||
|
||||
async retrieve(
|
||||
id: string,
|
||||
config?: FindConfig<Post>
|
||||
): Promise<Post> {
|
||||
const postRepo = this.activeManager_.withRepository(
|
||||
this.postRepository_
|
||||
)
|
||||
|
||||
const query = buildQuery({
|
||||
id,
|
||||
}, config)
|
||||
|
||||
const post = await postRepo.findOne(query)
|
||||
|
||||
if (!post) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
"Post was not found"
|
||||
)
|
||||
}
|
||||
|
||||
return post
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then, any other resources such as API Route or services that use this method can pass what relations to expand in the next parameter:
|
||||
|
||||
```ts
|
||||
await postService.retrieve(id, {
|
||||
relations: ["authors"],
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Throwing Errors
|
||||
|
||||
When you need to throw errors in your service methods, it's recommended to use `MedusaError` imported from `@medusajs/util`. That way, when an error is thrown within a request, the error will be returned in the response in a consistent format as Medusa's errors.
|
||||
|
||||
:::note
|
||||
|
||||
This assumes you're handling errors in your custom API Route as explained [here](../api-routes/create.mdx#handle-errors).
|
||||
|
||||
:::
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/services/post.ts"
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
|
||||
class PostService extends TransactionBaseService {
|
||||
// ...
|
||||
|
||||
async retrieve(
|
||||
id: string,
|
||||
config?: FindConfig<Post>
|
||||
): Promise<Post> {
|
||||
const postRepo = this.activeManager_.withRepository(
|
||||
this.postRepository_
|
||||
)
|
||||
|
||||
const query = buildQuery({
|
||||
id,
|
||||
}, config)
|
||||
|
||||
const post = await postRepo.findOne(query)
|
||||
|
||||
if (!post) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
"Post was not found"
|
||||
)
|
||||
}
|
||||
|
||||
return post
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use a Service
|
||||
|
||||
In this section, you'll learn how to use services throughout your Medusa backend. This includes both Medusa's services and your custom services.
|
||||
|
||||
:::note
|
||||
|
||||
Before using your service, make sure you run the [build command](#build-files).
|
||||
|
||||
:::
|
||||
|
||||
### In a Service
|
||||
|
||||
To use your custom service in another custom service, you can have easy access to it in the dependencies injected to the constructor of your service:
|
||||
|
||||
```ts
|
||||
class MyService extends TransactionBaseService {
|
||||
constructor(container) {
|
||||
super(container)
|
||||
this.postService = container.postService
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### In an API Route
|
||||
|
||||
To use your custom service in an API Route, you can use `MedusaRequest` object's `scope.resolve` method passing it the service’s registration name:
|
||||
|
||||
```ts
|
||||
const postService = req.scope.resolve("postService")
|
||||
|
||||
res.json({
|
||||
posts: postService.list(),
|
||||
})
|
||||
```
|
||||
|
||||
### In a Subscriber
|
||||
|
||||
To resolve your custom service in subscriber handler functions, use the `container` property of the handler function's parameter. The `container` has a method `resolve` which accepts the registration name of the service as a parameter.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
import {
|
||||
type SubscriberConfig,
|
||||
type SubscriberArgs,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
import { PostService } from "../services/post.ts"
|
||||
|
||||
export default async function postHandler({
|
||||
data, eventName, container, pluginOptions,
|
||||
}: SubscriberArgs<Record<string, string>>) {
|
||||
const postService: PostService = container.resolve(
|
||||
"postService"
|
||||
)
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
// ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<DetailsList
|
||||
sections={[
|
||||
{
|
||||
title: 'AwilixResolutionError: Could Not Resolve X',
|
||||
content: <ServiceLifetimeSection />
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## Example: Services with CRUD Operations
|
||||
|
||||
In this section, you'll find a full example of the `PostService` and `AuthorService` that implement Create, Read, Update, and Delete (CRUD) operations.
|
||||
|
||||
You can refer to the [Entities](../entities/create.mdx#adding-relations) documentation to learn how to create the custom entities used in this example.
|
||||
|
||||
<Tabs groupId="files" isCodeTabs={true}>
|
||||
<TabItem value="post" label="src/services/post.ts" default>
|
||||
|
||||
```ts
|
||||
import {
|
||||
FindConfig,
|
||||
Selector,
|
||||
TransactionBaseService,
|
||||
buildQuery,
|
||||
} from "@medusajs/medusa"
|
||||
import { PostRepository } from "../repositories/post"
|
||||
import { Post } from "../models/post"
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
|
||||
class PostService extends TransactionBaseService {
|
||||
protected postRepository_: typeof PostRepository
|
||||
|
||||
constructor(container) {
|
||||
super(container)
|
||||
this.postRepository_ = container.postRepository
|
||||
}
|
||||
|
||||
async listAndCount(
|
||||
selector?: Selector<Post>,
|
||||
config: FindConfig<Post> = {
|
||||
skip: 0,
|
||||
take: 20,
|
||||
relations: [],
|
||||
}): Promise<[Post[], number]> {
|
||||
const postRepo = this.activeManager_.withRepository(
|
||||
this.postRepository_
|
||||
)
|
||||
|
||||
const query = buildQuery(selector, config)
|
||||
|
||||
return postRepo.findAndCount(query)
|
||||
}
|
||||
|
||||
async list(
|
||||
selector?: Selector<Post>,
|
||||
config: FindConfig<Post> = {
|
||||
skip: 0,
|
||||
take: 20,
|
||||
relations: [],
|
||||
}): Promise<Post[]> {
|
||||
const [posts] = await this.listAndCount(selector, config)
|
||||
|
||||
return posts
|
||||
}
|
||||
|
||||
async retrieve(
|
||||
id: string,
|
||||
config?: FindConfig<Post>
|
||||
): Promise<Post> {
|
||||
const postRepo = this.activeManager_.withRepository(
|
||||
this.postRepository_
|
||||
)
|
||||
|
||||
const query = buildQuery({
|
||||
id,
|
||||
}, config)
|
||||
|
||||
const post = await postRepo.findOne(query)
|
||||
|
||||
if (!post) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
"Post was not found"
|
||||
)
|
||||
}
|
||||
|
||||
return post
|
||||
}
|
||||
|
||||
async create(
|
||||
data: Pick<Post, "title" | "author_id">
|
||||
): Promise<Post> {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const postRepo = manager.withRepository(
|
||||
this.postRepository_
|
||||
)
|
||||
const post = postRepo.create()
|
||||
post.title = data.title
|
||||
post.author_id = data.author_id
|
||||
const result = await postRepo.save(post)
|
||||
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
data: Omit<Partial<Post>, "id">
|
||||
): Promise<Post> {
|
||||
return await this.atomicPhase_(async (manager) => {
|
||||
const postRepo = manager.withRepository(
|
||||
this.postRepository_
|
||||
)
|
||||
const post = await this.retrieve(id)
|
||||
|
||||
Object.assign(post, data)
|
||||
|
||||
return await postRepo.save(post)
|
||||
})
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
return await this.atomicPhase_(async (manager) => {
|
||||
const postRepo = manager.withRepository(
|
||||
this.postRepository_
|
||||
)
|
||||
const post = await this.retrieve(id)
|
||||
|
||||
await postRepo.remove([post])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default PostService
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="author" label="src/services/author.ts">
|
||||
|
||||
```ts
|
||||
import {
|
||||
FindConfig,
|
||||
Selector,
|
||||
TransactionBaseService,
|
||||
buildQuery,
|
||||
} from "@medusajs/medusa"
|
||||
import { EntityManager } from "typeorm"
|
||||
import AuthorRepository from "../repositories/author"
|
||||
import { Author } from "../models/author"
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
|
||||
class AuthorService extends TransactionBaseService {
|
||||
protected manager_: EntityManager
|
||||
protected transactionManager_: EntityManager
|
||||
protected authorRepository_: typeof AuthorRepository
|
||||
|
||||
constructor(container) {
|
||||
super(container)
|
||||
this.authorRepository_ = container.authorRepository
|
||||
}
|
||||
|
||||
async listAndCount(
|
||||
selector?: Selector<Author>,
|
||||
config: FindConfig<Author> = {
|
||||
skip: 0,
|
||||
take: 20,
|
||||
relations: [],
|
||||
}): Promise<[Author[], number]> {
|
||||
const authorRepo = this.activeManager_.withRepository(
|
||||
this.authorRepository_
|
||||
)
|
||||
|
||||
const query = buildQuery(selector, config)
|
||||
|
||||
return authorRepo.findAndCount(query)
|
||||
}
|
||||
|
||||
async list(
|
||||
selector?: Selector<Author>,
|
||||
config: FindConfig<Author> = {
|
||||
skip: 0,
|
||||
take: 20,
|
||||
relations: [],
|
||||
}): Promise<Author[]> {
|
||||
const [authors] = await this.listAndCount(selector, config)
|
||||
|
||||
return authors
|
||||
}
|
||||
|
||||
async retrieve(
|
||||
id: string,
|
||||
config?: FindConfig<Author>
|
||||
): Promise<Author> {
|
||||
const authorRepo = this.activeManager_.withRepository(
|
||||
this.authorRepository_
|
||||
)
|
||||
|
||||
const query = buildQuery({
|
||||
id,
|
||||
}, config)
|
||||
|
||||
const author = await authorRepo.findOne(query)
|
||||
|
||||
if (!author) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
"Author was not found"
|
||||
)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
async create(
|
||||
data: Pick<Author, "name" | "image">
|
||||
): Promise<Author> {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const authorRepo = manager.withRepository(
|
||||
this.authorRepository_
|
||||
)
|
||||
const post = authorRepo.create(data)
|
||||
const result = await authorRepo.save(post)
|
||||
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
data: Omit<Partial<Author>, "id">
|
||||
): Promise<Author> {
|
||||
return await this.atomicPhase_(async (manager) => {
|
||||
const authorRepo = manager.withRepository(
|
||||
this.authorRepository_
|
||||
)
|
||||
const post = await this.retrieve(id)
|
||||
|
||||
Object.assign(post, data)
|
||||
|
||||
return await authorRepo.save(post)
|
||||
})
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
return await this.atomicPhase_(async (manager) => {
|
||||
const authorRepo = manager.withRepository(
|
||||
this.authorRepository_
|
||||
)
|
||||
const post = await this.retrieve(id)
|
||||
|
||||
await authorRepo.remove([post])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default AuthorService
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Create a Plugin](../plugins/create.mdx)
|
||||
@@ -1,101 +0,0 @@
|
||||
---
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import DetailsList from '@site/src/components/DetailsList'
|
||||
import ServiceLifetimeSection from '../../troubleshooting/awilix-resolution-error/_service-lifetime.md'
|
||||
|
||||
# How to Extend a Service
|
||||
|
||||
In this document, you’ll learn how to extend a core service in Medusa.
|
||||
|
||||
## Overview
|
||||
|
||||
Medusa’s core services cover a wide range of functionalities related to each domain or entity. You can extend these services to add custom methods or override existing methods.
|
||||
|
||||
### Word of Caution about Overriding
|
||||
|
||||
Extending services to add new methods shouldn't cause any issues within your commerce application. However, if you extend them to override their existing methods, you should be aware that this could have negative implications, such as unanticipated bugs, especially when you try to upgrade the core Medusa package to a newer version.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create the Service File
|
||||
|
||||
In your Medusa backend, create the file `src/services/product.ts`. This file will hold your extended service.
|
||||
|
||||
Note that the name of the file must be the same as the name of the original service in the core package. So, if you’re extending the `ProductService`, the file’s name should be `product.ts`. On the other hand, if you’re extending the `CustomerService`, the file’s name should be `customer.ts`.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Implementing the Service
|
||||
|
||||
In the file, you can import the original service from the Medusa core, then create your service that extends the core service.
|
||||
|
||||
For example, to extend the Product service:
|
||||
|
||||
```ts title="src/services/product.ts"
|
||||
import {
|
||||
ProductService as MedusaProductService,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
class ProductService extends MedusaProductService {
|
||||
// TODO add customizations
|
||||
}
|
||||
|
||||
export default ProductService
|
||||
```
|
||||
|
||||
Notice that you alias the `ProductService` of the core to avoid naming conflicts.
|
||||
|
||||
Within the service, you can add new methods or extend existing ones.
|
||||
|
||||
You can also change the lifetime of the service:
|
||||
|
||||
```ts title="src/services/product.ts"
|
||||
import { Lifetime } from "awilix"
|
||||
import {
|
||||
ProductService as MedusaProductService,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
class ProductService extends MedusaProductService {
|
||||
// The default life time for a core service is SINGLETON
|
||||
static LIFE_TIME = Lifetime.SCOPED
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default ProductService
|
||||
```
|
||||
|
||||
You can learn more details about the service lifetime and other considerations when creating a service in the [Create Service documentation](./create-service.mdx).
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Test it Out
|
||||
|
||||
To test out your customization, start by transpiling your files by running the following command in the root directory of the Medusa backend:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
Then, start the backend:
|
||||
|
||||
```bash npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
You should see the customizations you made in effect.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<DetailsList
|
||||
sections={[
|
||||
{
|
||||
title: 'AwilixResolutionError: Could Not Resolve X (Service Lifetime)',
|
||||
content: <ServiceLifetimeSection />
|
||||
}
|
||||
]}
|
||||
/>
|
||||
@@ -1,63 +0,0 @@
|
||||
---
|
||||
description: 'Learn what Services are in Medusa. Services represent bundled helper methods that you want to use across your commerce application.'
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Services
|
||||
|
||||
In this document, you'll learn about what Services are in Medusa.
|
||||
|
||||
## What are Services
|
||||
|
||||
Services in Medusa represent bundled helper methods that you want to use across your commerce application. By convention, they represent a certain entity or functionality in Medusa.
|
||||
|
||||
For example, you can use Medusa’s `productService` to get the list of products, as well as perform other functionalities related to products. There’s also an `authService` that provides functionalities like authenticating customers and users.
|
||||
|
||||
In the Medusa backend, custom services are TypeScript or JavaScript files located in the `src/services` directory. Each service should be a class that extends the `TransactionBaseService` class from the core Medusa package `@medusajs/medusa`. Each file you create in `src/services` should hold one service and export it.
|
||||
|
||||
The file name is important as it determines the name of the service when you need to use it elsewhere. The name of the service will be registered in the dependency container as the camel-case version of the file name with `Service` appended to the end of the name. Other resources, such as other services or API Routes, will use that name when resolving the service from the dependency container.
|
||||
|
||||
For example, if the file name is `hello.ts`, the service will be registered as `helloService` in the dependency container. If the file name is `hello-world.ts`, the service name will be registered as `helloWorldService`.
|
||||
|
||||
:::note
|
||||
|
||||
You can learn more about the dependency container and how it works in the [dependency injection](../fundamentals/dependency-injection.md) documentation.
|
||||
|
||||
:::
|
||||
|
||||
The service must then be transpiled using the `build` command, which moves them to the `dist` directory, to be used across your commerce application.
|
||||
|
||||
:::tip
|
||||
|
||||
If you're creating a service in a plugin, learn more about the required structure [here](../plugins/create.mdx#plugin-structure).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Custom Development
|
||||
|
||||
Developers can create custom services in the Medusa backend, a plugin, or in a module.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/services/create-service',
|
||||
label: 'Create a Service',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a service in Medusa.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/services/extend-service',
|
||||
label: 'Extend a Service',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to extend a core Medusa service.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
@@ -1,81 +0,0 @@
|
||||
---
|
||||
description: "Learn how to override a strategy in a Medusa backend or plugin."
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Override a Strategy
|
||||
|
||||
In this document, you’ll learn how to override a strategy in a Medusa backend or plugin.
|
||||
|
||||
## Overview
|
||||
|
||||
The Medusa core defines and uses strategies for certain functionalities, which allows developers to override these functionalities and implement them as fits for their use case.
|
||||
|
||||
For example, the cart completion process is implemented within a `CartCompletionStrategy` that is used inside the Complete Cart API Route. If you need to change the cart completion process, you can override the `CartCompletionStrategy` and implement your own strategy. The Medusa backend will then use your strategy instead of the one defined in the core.
|
||||
|
||||
### Hierarchy of Strategy Resolution
|
||||
|
||||
When the Medusa backend starts, the strategies defined in the core are registered in the dependency container first.
|
||||
|
||||
If a strategy is overridden in a plugin, it will be registered in the dependency container in place of the original strategy.
|
||||
|
||||
If a strategy is overridden in the Medusa backend codebase, it will be registered in the dependency container in place of the original strategy.
|
||||
|
||||
So, if a strategy is overridden within a plugin and in the Medusa backend codebase, the one in the codebase is registered and used instead of the one in the plugin.
|
||||
|
||||
:::tip
|
||||
|
||||
You can learn more about the dependency container and injection [here](../fundamentals/dependency-injection.md).
|
||||
|
||||
:::
|
||||
|
||||
### Existing Resources
|
||||
|
||||
This guide explains how to override a strategy in general. It doesn’t explain how to override specific strategies.
|
||||
|
||||
There are other resources that provide steps specific to a strategy type:
|
||||
|
||||
- [How to override the Cart Completion Strategy](../../modules/carts-and-checkout/backend/cart-completion-strategy.md)
|
||||
- [How to override the Tax Calculation Strategy](../../references/tax_calculation/classes/tax_calculation.AbstractTaxCalculationStrategy.mdx)
|
||||
- [How to override the Price Selection Strategy](/modules/price-lists/price-selection-strategy)
|
||||
- [How to override a Batch Job Strategy](../batch-jobs/customize-import.md)
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create Strategy Class
|
||||
|
||||
A strategy class is created in a TypeScript or JavaScript file in the `src/strategies` directory of your Medusa backend or plugin.
|
||||
|
||||
The class must extend or implement a strategy interface or class from within the core, depending on what strategy you’re overriding. For example, if you’re overriding the `CartCompletionStrategy`, you must extend the `AbstractCartCompletionStrategy`.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Implement Functionalities
|
||||
|
||||
Each strategy class is required to implement certain methods defined by the interface it implements or the abstract class it extends. Depending on what the strategy is used for, you must implement the functionalities in each of the required methods.
|
||||
|
||||
For example, if you’re overriding the `CartCompletionStrategy`, you must implement the `complete` method using the signature defined in `AbstractCartCompletionStrategy`.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Run Build Command
|
||||
|
||||
In the directory of the Medusa backend, run the `build` command to transpile the files in the `src` directory into the `dist` directory:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test it Out
|
||||
|
||||
Run your backend to test it out:
|
||||
|
||||
```bash npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
You can test now whether your strategy is working by performing the actions that run your strategy.
|
||||
|
||||
For example, if you’re overriding the `CartCompletionStrategy`, you can simulate the cart completion flow and see if your strategy is being used.
|
||||
@@ -1,57 +0,0 @@
|
||||
---
|
||||
description: "Learn what a Strategy is in Medusa. A strategy is an isolated piece of business logic that can be overridden and customized."
|
||||
---
|
||||
|
||||
import DocCard from '@theme/DocCard';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Strategy
|
||||
|
||||
In this document, you’ll learn what a Strategy is in Medusa.
|
||||
|
||||
## Introduction
|
||||
|
||||
A strategy is an isolated piece of business logic that can be overridden and customized. It’s a TypeScript or JavaScript class that doesn’t have to follow a specific definition, but performs only a single functionality.
|
||||
|
||||
For example, in the core `@medusajs/medusa` package, strategies are used to implement functionalities like cart completion and product import.
|
||||
|
||||
These strategy classes are then resolved in API Routes, services, or wherever needed using dependency injection and used to perform their designated functionality.
|
||||
|
||||
For example, the `CartCompletionStrategy` is resolved in the Complete Cart API Route that is defined in the core `@medusajs/medusa` package. It’s then used to complete the cart and place the order:
|
||||
|
||||
```ts
|
||||
export default async (req, res) => {
|
||||
// ...
|
||||
const completionStrat: AbstractCartCompletionStrategy =
|
||||
req.scope.resolve(
|
||||
"cartCompletionStrategy"
|
||||
)
|
||||
|
||||
const {
|
||||
response_code,
|
||||
response_body,
|
||||
} = await completionStrat.complete(
|
||||
id,
|
||||
idempotencyKey,
|
||||
req.request_context
|
||||
)
|
||||
|
||||
res.status(response_code).json(response_body)
|
||||
}
|
||||
```
|
||||
|
||||
When a strategy is overridden, dependency injection then resolves the strategy (in the code example above, `CartCompletionStrategy`) to the custom strategy that the developer created. Then, the method (in the code example above, `complete`) of the custom strategy will be executed instead of the one defined in the core package.
|
||||
|
||||
## Custom Development
|
||||
|
||||
Developers can override strategies defined in the core to customize their functionality within their Medusa backend codebase or within a plugin.
|
||||
|
||||
<DocCard item={{
|
||||
type: 'link',
|
||||
href: '/development/strategies/override-strategy',
|
||||
label: 'Backend: Override a Strategy',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to override a strategy.',
|
||||
}
|
||||
}} />
|
||||
@@ -1,52 +0,0 @@
|
||||
# Worker Mode for a Medusa Instance
|
||||
|
||||
In this document, you'll learn about the worker mode in Medusa and its importance in production.
|
||||
|
||||
## What is a Worker?
|
||||
|
||||
Workers are processes running separately from the main application. They're useful for executing long-running or resource-heavy tasks in the background, such as importing products or updating a search index.
|
||||
|
||||
With a worker, these tasks are offloaded to a separate process. So, they won't affect the performance of the main application.
|
||||
|
||||

|
||||
|
||||
While workers are especially good for intensive tasks, it's recommended to always configuring a worker process in your setup if possible.
|
||||
|
||||
---
|
||||
|
||||
## Medusa Runtimes
|
||||
|
||||
Medusa has three runtime modes:
|
||||
|
||||
- `server`: the API Routes are registered, and no workers are started.
|
||||
- `worker`: the API Routes aren't registered, and workers are started.
|
||||
- `shared`: (default) the API routes are registered, and workers are started.
|
||||
|
||||
The runtime mode is configured by the [worker_mode configuration](../../references/medusa_config/interfaces/medusa_config.ConfigModule.mdx#worker_mode) in `medusa-config.js`.
|
||||
|
||||
For example:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const projectConfig = {
|
||||
// ...,
|
||||
database_url: "...",
|
||||
worker_mode: "worker",
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage in Production
|
||||
|
||||
When deploying the Medusa backend in production, it's recommended to deploy two instances:
|
||||
|
||||
1. One having the `worker_mode` configuration set to `server`.
|
||||
2. Another having the `worker_mode` configuration set to `worker`.
|
||||
|
||||
In this case, it's best to set the `worker_mode` configuration's value to an environment variable, then set that environment variable differently for each deployed instance. For development, you can use `shared` as the environment variable's values.
|
||||
|
||||
:::tip
|
||||
|
||||
Check out the [Railway Deployment Guide](../../deployments/server/deploying-on-railway.mdx) as an example.
|
||||
|
||||
:::
|
||||
@@ -1,443 +0,0 @@
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import DocCard from '@theme/DocCard';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Workflows Introduction
|
||||
|
||||
A workflow is a series of queries and actions that complete a task. Workflows are made up of a series of steps that interact with Medusa’s commerce modules, custom services, or external systems.
|
||||
|
||||
You construct a Workflow similar to how you create a JavaScript function, but unlike regular functions, a Medusa Workflow creates an internal representation of your steps. This makes it possible to keep track of your Workflow’s progress, automatically retry failing steps, and, if necessary, roll back steps.
|
||||
|
||||
Workflows can be used to define a flow with interactions across multiple systems, integrate third-party services into your commerce application, or automate actions within your application. Any flow with a series of steps can be implemented as a workflow.
|
||||
|
||||
## Example: Your First Workflow
|
||||
|
||||
The tools to build Workflows are installed by default in Medusa projects. For other Node.js projects, you can install the `@medusajs/workflows-sdk` package from npm.
|
||||
|
||||
### Create a Step
|
||||
|
||||
A workflow is made of a series of steps. A step is created using the `createStep` utility function.
|
||||
|
||||
Create the file `src/workflows/hello-world.ts` with the following content:
|
||||
|
||||
```ts title=src/workflows/hello-world.ts
|
||||
import {
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
|
||||
const step1 = createStep("step-1", async () => {
|
||||
return new StepResponse(`Hello from step one!`)
|
||||
})
|
||||
```
|
||||
|
||||
This creates one step that returns a hello message.
|
||||
|
||||
### Create a Workflow
|
||||
|
||||
Next, you can create a workflow using the `createWorkflow` function:
|
||||
|
||||
```ts title=src/workflows/hello-world.ts
|
||||
import {
|
||||
createStep,
|
||||
StepResponse,
|
||||
createWorkflow,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
|
||||
type WorkflowOutput = {
|
||||
message: string
|
||||
}
|
||||
|
||||
const step1 = createStep("step-1", async () => {
|
||||
return new StepResponse(`Hello from step one!`)
|
||||
})
|
||||
|
||||
const myWorkflow = createWorkflow<unknown, WorkflowOutput>(
|
||||
"hello-world",
|
||||
function () {
|
||||
const str1 = step1()
|
||||
|
||||
return {
|
||||
message: str1,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export default myWorkflow
|
||||
```
|
||||
|
||||
This creates a `hello-world` workflow. When you create a workflow, it’s constructed but not executed yet.
|
||||
|
||||
### Execute the Workflow
|
||||
|
||||
You can execute a workflow from different places within Medusa.
|
||||
|
||||
- Use API Routes if you want the workflow to execute in response to an API request or a webhook.
|
||||
- Use Subscribers if you want to execute a workflow when an event is triggered.
|
||||
- Use Scheduled Jobs if you want your workflow to execute on a regular schedule.
|
||||
|
||||
To execute the workflow, invoke it passing the Medusa container as a parameter, then use its `run` method:
|
||||
|
||||
<Tabs groupId="resource-type" isCodeTabs={true}>
|
||||
<TabItem value="api-route" label="API Route" attributes={{
|
||||
title: "src/api/store/custom/route.ts"
|
||||
}} default>
|
||||
|
||||
```ts
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse
|
||||
} from "@medusajs/medusa";
|
||||
import myWorkflow from "../../../workflows/hello-world";
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const { result } = await myWorkflow(req.scope)
|
||||
.run()
|
||||
|
||||
res.send(result)
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="subscribers" label="Subscribers" attributes={{
|
||||
title: "src/subscribers/create-customer.ts"
|
||||
}}>
|
||||
|
||||
```ts
|
||||
import {
|
||||
type SubscriberConfig,
|
||||
type SubscriberArgs,
|
||||
CustomerService,
|
||||
Customer,
|
||||
} from "@medusajs/medusa"
|
||||
import myWorkflow from "../workflows/hello-world"
|
||||
|
||||
export default async function handleCustomerCreate({
|
||||
data, eventName, container, pluginOptions
|
||||
}: SubscriberArgs<Customer>) {
|
||||
myWorkflow(container)
|
||||
.run()
|
||||
.then(({ result }) => {
|
||||
console.log(
|
||||
`New user: ${result.message}`
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export const config: SubscriberConfig = {
|
||||
event: CustomerService.Events.CREATED,
|
||||
context: {
|
||||
subscriberId: "hello-customer"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="job" label="Scheduled Job" attributes={{
|
||||
title: "src/jobs/message-daily.ts"
|
||||
}}>
|
||||
|
||||
```ts
|
||||
import {
|
||||
type ScheduledJobConfig,
|
||||
type ScheduledJobArgs,
|
||||
} from "@medusajs/medusa"
|
||||
import myWorkflow from "../workflows/hello-world"
|
||||
|
||||
export default async function handler({
|
||||
container,
|
||||
data,
|
||||
pluginOptions,
|
||||
}: ScheduledJobArgs) {
|
||||
myWorkflow(container)
|
||||
.run()
|
||||
.then(({ result }) => {
|
||||
console.log(
|
||||
result.message
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export const config: ScheduledJobConfig = {
|
||||
name: "run-once-a-day",
|
||||
schedule: "0 0 * * *",
|
||||
data: {},
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
If you run your backend and trigger the execution of the workflow (based on where you’re executing it), you should see the message `Hello from step one!`.
|
||||
|
||||
### Pass Inputs to Steps
|
||||
|
||||
Steps in a workflow can accept parameters.
|
||||
|
||||
For example, create a new step that accepts an input and returns a message with that input:
|
||||
|
||||
```ts title=src/workflows/hello-world.ts
|
||||
type WorkflowInput = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
const step2 = createStep(
|
||||
"step-2",
|
||||
async ({ name }: WorkflowInput) => {
|
||||
return new StepResponse(`Hello ${name} from step two!`)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
Then, update the workflow to accept input and pass it to the new step:
|
||||
|
||||
```ts title=src/workflows/hello-world.ts
|
||||
import {
|
||||
// previous imports
|
||||
transform,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
|
||||
// ...
|
||||
|
||||
const myWorkflow = createWorkflow<
|
||||
WorkflowInput,
|
||||
WorkflowOutput
|
||||
>("hello-world", function (input) {
|
||||
const str1 = step1()
|
||||
const str2 = step2(input)
|
||||
|
||||
const result = transform(
|
||||
{
|
||||
str1,
|
||||
str2,
|
||||
},
|
||||
(input) => ({
|
||||
message: `${input.str1}\n${input.str2}`,
|
||||
})
|
||||
)
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
export default myWorkflow
|
||||
```
|
||||
|
||||
Notice that to use the results of the steps, you must use the `transform` utility function. It gives you access to the real-time results of the steps once the workflow is executed.
|
||||
|
||||
Then, pass the necessary input to the workflow when you execute it. For example, in the API Route executing the workflow:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
import myWorkflow from "../../../workflows/hello-world"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const { result } = await myWorkflow(req.scope).run({
|
||||
input: {
|
||||
name: req.validatedQuery.name as string,
|
||||
},
|
||||
})
|
||||
|
||||
res.send(result)
|
||||
}
|
||||
```
|
||||
|
||||
If you execute the workflow again, you’ll see:
|
||||
|
||||
- A `Hello from step one!` message, indicating that step one ran first.
|
||||
- A `Hello {name} from step two` message, indicating that step two ran after.
|
||||
|
||||
### Add Error Handling
|
||||
|
||||
Errors can occur in a workflow. To avoid data inconsistency, you can pass a compensation function as a third parameter to the `createStep` function.
|
||||
|
||||
The compensation function only runs if an error occurs throughout the Workflow. It’s useful to undo or roll back actions you’ve performed in a step.
|
||||
|
||||
For example, change step one to add a compensation function and step two to throw an error:
|
||||
|
||||
```ts title=src/workflows/hello-world.ts
|
||||
const step1 = createStep(
|
||||
"step-1",
|
||||
async () => {
|
||||
const message = `Hello from step one!`
|
||||
|
||||
console.log(message)
|
||||
|
||||
return new StepResponse(message)
|
||||
},
|
||||
async () => {
|
||||
console.log("Oops! Rolling back my changes...")
|
||||
}
|
||||
)
|
||||
|
||||
const step2 = createStep(
|
||||
"step-2",
|
||||
async ({ name }: WorkflowInput) => {
|
||||
throw new Error("Throwing an error...")
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
If you execute the Workflow, you should see:
|
||||
|
||||
- `Hello from step one!` logged, indicating that the first step ran successfully.
|
||||
- `Oops! Rolling back my changes...` logged, indicating that the second step failed and the compensation function of the first step ran consequently.
|
||||
|
||||
:::note[Try it Out]
|
||||
|
||||
You can try out this guide on [Stackblitz](https://stackblitz.com/edit/stackblitz-starters-etznpy?file=compensation-demo.ts&view=editor).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## More Advanced Example
|
||||
|
||||
Let’s cover a more realistic example.
|
||||
|
||||
For example, you can build a workflow that updates a product’s CMS details both in Medusa and an external CMS service:
|
||||
|
||||
```ts
|
||||
import { createWorkflow } from "@medusajs/workflows-sdk"
|
||||
import { Product } from "@medusajs/medusa"
|
||||
import { updateProduct, sendProductDataToCms } from "./steps"
|
||||
|
||||
type WorkflowInput = {
|
||||
id: string
|
||||
title: string,
|
||||
description: string,
|
||||
images: string[]
|
||||
}
|
||||
|
||||
const updateProductCmsWorkflow = createWorkflow<
|
||||
WorkflowInput,
|
||||
Product
|
||||
>("update-product-cms", function (input) {
|
||||
const product = updateProduct(input)
|
||||
sendProductDataToCms(product)
|
||||
|
||||
return product
|
||||
})
|
||||
```
|
||||
|
||||
As these steps are making changes to data in the Medusa backend and a third-party service, it’s useful to provide a compensation function for each step that rolls back the changes.
|
||||
|
||||
For example, you can pass a compensation function to the `updateProduct` step that reverts the product update in case an error occurs:
|
||||
|
||||
```ts
|
||||
const updateProduct = createStep(
|
||||
"update-product",
|
||||
async (input: WorkflowInput, context) => {
|
||||
const productService: ProductService =
|
||||
context.container.resolve("productService")
|
||||
|
||||
const { id, ...updateData } = input
|
||||
const previousProductData = await productService.retrieve(
|
||||
id, {
|
||||
select: ["title", "description", "images"],
|
||||
}
|
||||
)
|
||||
const product = await productService.update(id, updateData)
|
||||
|
||||
return new StepResponse(product, {
|
||||
id,
|
||||
previousProductData,
|
||||
})
|
||||
}, async ({ id, previousProductData }, context) => {
|
||||
const productService: ProductService =
|
||||
context.container.resolve("productService")
|
||||
|
||||
await productService.update(id, previousProductData)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
Your steps may interact with external systems. For example, the `sendProductDataToCms` step communicates with an external CMS service. With the error handling and roll-back features that workflows provide, developers can ensure data delivery between multiple systems in their stack.
|
||||
|
||||
---
|
||||
|
||||
## Constraints on Workflow Constructor Function
|
||||
|
||||
The Workflow Builder, `createWorkflow`, comes with a set of constraints:
|
||||
|
||||
- The function passed to the `createWorkflow` can’t be an arrow function:
|
||||
|
||||
```ts
|
||||
// Don't
|
||||
const myWorkflow = createWorkflow<
|
||||
WorkflowInput,
|
||||
WorkflowOutput
|
||||
>("hello-world", (input) => {
|
||||
// ...
|
||||
}
|
||||
)
|
||||
|
||||
// Do
|
||||
const myWorkflow = createWorkflow<WorkflowInput, WorkflowOutput>
|
||||
("hello-world", function (input) {
|
||||
// ...
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
- The function passed to the `createWorkflow` can’t be an asynchronous function.
|
||||
- Since the constructor function only defines how the workflow works, you can’t directly manipulate data within the function. To do that, you must use the `transform` function:
|
||||
|
||||
```ts
|
||||
// Don't
|
||||
const myWorkflow = createWorkflow<
|
||||
WorkflowInput,
|
||||
WorkflowOutput
|
||||
>("hello-world", function (input) {
|
||||
const str1 = step1(input)
|
||||
const str2 = step2(input)
|
||||
|
||||
return {
|
||||
message: `${input.str1}${input.str2}`,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Do
|
||||
const myWorkflow = createWorkflow<
|
||||
WorkflowInput,
|
||||
WorkflowOutput
|
||||
>("hello-world", function (input) {
|
||||
const str1 = step1(input)
|
||||
const str2 = step2(input)
|
||||
|
||||
const result = transform({
|
||||
str1,
|
||||
str2,
|
||||
}, (input) => ({
|
||||
message: `${input.str1}${input.str2}`,
|
||||
}))
|
||||
|
||||
return result
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
<DocCard item={{
|
||||
type: 'link',
|
||||
href: '/references/workflows',
|
||||
label: 'Workflows API Reference',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn about workflow\'s utility methods.'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@@ -1,23 +0,0 @@
|
||||
# Experimental Features
|
||||
|
||||
This section of the documentation includes features that are currently experimental.
|
||||
|
||||
As Medusa moves towards modularization, commerce concepts such as Products or Pricing will be moved to isolated commerce modules shipped as NPM packages. This significantly changes Medusa's architecture, making it more flexible for custom digital commerce applications.
|
||||
|
||||
## Enabling Experimental Features
|
||||
|
||||
:::danger[Production Warning]
|
||||
|
||||
All features guarded by the `medusa_v2` flag are not ready for production and will cause unexpected issues in your production server. Additionally, the core Store and Admin API routes are not registered if the flag is enabled – only routes in the `dist/api-v2/*` directory of the `@medusajs/medusa` package.
|
||||
|
||||
:::
|
||||
|
||||
To enable the experimental features:
|
||||
|
||||
1. [Enable the `medusa_v2` feature flag](../development/feature-flags/toggle.md) in your backend.
|
||||
2. Install the [Product](./product/install-medusa.mdx) and [Pricing](./pricing/install-medusa.mdx) modules and add them to the Medusa configurations.
|
||||
3. Finally, run migrations in your backend with the following command:
|
||||
|
||||
```bash
|
||||
npx medusa migrations run
|
||||
```
|
||||
@@ -1,83 +0,0 @@
|
||||
# Pricing Concepts
|
||||
|
||||
In this document, you’ll learn about the main concepts in the Pricing Module, and how data is stored and related.
|
||||
|
||||
## Money Amount
|
||||
|
||||
A `MoneyAmount` represents a price.
|
||||
|
||||
Money amounts can be conditioned by the `min_quantity` and `max_quantity` attributes, which are helpful when calculating the price for a specific quantity.
|
||||
|
||||
If a money amount has its `min_quantity` or `max_quantity` attributes set, they’re only considered for the price calculation if they have a lower `min_quantity` or a higher `max_quantity` than the quantity specified for calculation.
|
||||
|
||||
---
|
||||
|
||||
## Price Set
|
||||
|
||||
A `PriceSet` represents a collection of money amounts that are linked to a resource (for example, a product or a shipping option). The Price Set and Money Amount relationship is represented by the `PriceSetMoneyAmount` entity.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Prices with Rules
|
||||
|
||||
### Rule Type
|
||||
|
||||
Each money amount within a price set can be a price that’s applied for different conditions. These conditions are represented as rule types.
|
||||
|
||||
A `RuleType` defines custom conditions. Each rule type has a unique `rule_attribute`, referenced in rule values, such as when setting a rule of a money amount.
|
||||
|
||||
### Price Rule
|
||||
|
||||
Each rule of a money amount within a price set is represented by the `PriceRule` entity, which holds the value of a rule type. The `PriceSetMoneyAmount` has a `rules_count` attribute, which indicates how many rules, represented by `PriceRule`, are applied to the money amount.
|
||||
|
||||

|
||||
|
||||
For example, you can create a `zip_code` rule type. Then, a money amount within the price set can have the rule value `zip_code: 10557`, indicating that the money amount can only be applied within the `10557` zip code.
|
||||
|
||||
Each money amount within the price set can have different values for the same rule type.
|
||||
|
||||
For example, this diagram showcases two money amounts having different values for the same rule type:
|
||||
|
||||

|
||||
|
||||
Each money amount can have multiple rules applied to it as well.
|
||||
|
||||
For example, a money amount can have the rules `zip_code` and `region_id` applied to it. In this case, the value of each rule is represented by a `PriceRule`.
|
||||
|
||||

|
||||
|
||||
### PriceSetRuleType
|
||||
|
||||
The `PriceSetRuleType` entity indicates what rules the money amounts can have within a price set. It creates a relation between the `PriceSet` and `RuleType` entities.
|
||||
|
||||
For example, to use the `zip_code` rule type on a money amount in a price set, the rule type must first be enabled on the price set through the `PriceSetRuleType`.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Price List
|
||||
|
||||
A `PriceList` is a group of prices only enabled if their rules are satisfied. A price list has optional `start_date` and `end_date` attributes, which indicate the date range in which a price list can be applied.
|
||||
|
||||
Its associated prices are represented by the `PriceSetMoneyAmount` entity, which is used to store the money amounts of a price set.
|
||||
|
||||
Each rule that can be applied to a price list is represented by the `PriceListRule` entity. The `rules_count` attribute of a `PriceList` indicates how many rules are applied to it.
|
||||
|
||||
Each rule of a price list can have more than one value, representing its values by the `PriceListRuleValue` entity.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Use Case Example: Pricing and Product Modules
|
||||
|
||||
In a real use case, you would use the Pricing Module with your custom logic or other Medusa Commerce Modules, such as the Product Module.
|
||||
|
||||
When used with the Product Module, a product variant’s prices are stored as money amounts belonging to a price set. A relation is formed between the `ProductVariant` and the `PriceSet` when the modules are linked.
|
||||
|
||||

|
||||
|
||||
So, when you want to add prices for a product variant, you create a price set and add the prices as money amounts to it. You can then benefit from adding rules to prices or using the `calculatePrices` method to retrieve the price of a product variant within a specified context.
|
||||
Loaded 100 of 6187 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user