Skip to content

Commit

Permalink
docs: intro + basic chapter fixes (#10361)
Browse files Browse the repository at this point in the history
  • Loading branch information
shahednasser authored Dec 2, 2024
1 parent a1f36ca commit b4d6a4b
Show file tree
Hide file tree
Showing 17 changed files with 105 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ You create a UI route in a `page.tsx` file under a sub-directory of `src/admin/r

For example, create the file `src/admin/routes/custom/page.tsx` with the following content:

![Example of UI route file in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732867243/Medusa%20Book/ui-route-dir-overview_tgju25.jpg)

```tsx title="src/admin/routes/custom/page.tsx"
import { Container, Heading } from "@medusajs/ui"

Expand Down Expand Up @@ -110,6 +112,8 @@ The above example adds a new sidebar item with the label `Custom Route` and an i

Consider that along the UI route above at `src/admin/routes/custom/page.tsx` you create a nested UI route at `src/admin/routes/custom/nested/page.tsx` that also exports route configurations:

![Example of nested UI route file in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732867243/Medusa%20Book/ui-route-dir-overview_tgju25.jpg)

```tsx title="src/admin/routes/custom/nested/page.tsx"
import { defineRouteConfig } from "@medusajs/admin-sdk"
import { Container, Heading } from "@medusajs/ui"
Expand Down Expand Up @@ -149,6 +153,8 @@ To create a page under the settings section of the admin dashboard, create a UI

For example, create a UI route at `src/admin/routes/settings/custom/page.tsx`:

![Example of settings UI route file in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732867435/Medusa%20Book/setting-ui-route-dir-overview_kytbh8.jpg)

```tsx title="src/admin/routes/settings/custom/page.tsx"
import { defineRouteConfig } from "@medusajs/admin-sdk"
import { Container, Heading } from "@medusajs/ui"
Expand Down Expand Up @@ -180,6 +186,8 @@ A UI route can accept path parameters if the name of any of the directories in i

For example, create the file `src/admin/routes/custom/[id]/page.tsx` with the following content:

![Example of UI route file with path parameters in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732867748/Medusa%20Book/path-param-ui-route-dir-overview_kcfbev.jpg)

```tsx title="src/admin/routes/custom/[id]/page.tsx" highlights={[["5", "", "Retrieve the path parameter."], ["10", "{id}", "Show the path parameter."]]}
import { useParams } from "react-router-dom"
import { Container, Heading } from "@medusajs/ui"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ You create a widget in a `.tsx` file under the `src/admin/widgets` directory. Th

For example, create the file `src/admin/widgets/product-widget.tsx` with the following content:

![Example of widget file in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732867137/Medusa%20Book/widget-dir-overview_dqsbct.jpg)

export const widgetHighlights = [
["5", "ProductWidget", "The React component of the product widget."],
["17", "zone", "The zone to inject the widget to."]
Expand Down
4 changes: 3 additions & 1 deletion www/apps/book/app/learn/basics/api-routes/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ In this chapter, you’ll learn what API Routes are and how to create them.

## What is an API Route?

An API Route is a REST API endpoint. It exposes commerce features to external applications, such as storefronts, the admin dashboard, or third-party systems.
An API Route is an endpoint. It exposes commerce features to external applications, such as storefronts, the admin dashboard, or third-party systems.

The Medusa core application provides a set of admin and store API routes out-of-the-box. You can also create custom API routes to expose your custom functionalities.

Expand All @@ -18,6 +18,8 @@ The Medusa core application provides a set of admin and store API routes out-of-

An API Route is created in a TypeScript or JavaScript file under the `src/api` directory of your Medusa application. The file’s name must be `route.ts` or `route.js`.

![Example of API route in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732808645/Medusa%20Book/route-dir-overview_dqgzmk.jpg)

Each file exports API Route handler functions for at least one HTTP method (`GET`, `POST`, `DELETE`, etc…).

For example, to create a `GET` API Route at `/hello-world`, create the file `src/api/hello-world/route.ts` with the following content:
Expand Down
15 changes: 7 additions & 8 deletions www/apps/book/app/learn/basics/commerce-modules/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ In this chapter, you'll learn about Medusa's commerce modules.

## What is a Commerce Module?

A commerce module is a package built by Medusa that provides business logic and data models specific for a single commerce domain, such as the Product and Order modules. Commerce modules are available out-of-the-box in your application.
Commerce modules are built-in [modules](../modules/page.mdx) of Medusa that provide core commerce logic specific to domains like Products, Orders, Customers, Fulfillment, and much more.

Medusa implements core commerce flows in workflows that use the commerce modules. Then, it exposes admin and storefront API routes that, under the hood, execute these workflows.

For example, the workflow to add a product to the cart uses the Product Module to check if the product exists, the Inventory Module to ensure the product is available in the inventory, and the Cart Module to finally add the product to the cart.
Medusa's commerce modules are used to form Medusa's default [workflows](!resources!/medusa-workflows-reference) and [APIs](!api!/store). For example, when you call the add to cart endpoint. the add to cart workflow runs which uses the Product Module to check if the product exists, the Inventory Module to ensure the product is available in the inventory, and the Cart Module to finally add the product to the cart.

<Note title="Tip">

You'll find the details and steps of the add-to-cart workflow in [this workflow reference](!resources!/references/medusa-workflows/addToCartWorkflow)

</Note>

The core commerce logic contained in Commerce Modules is also available directly when you are building customizations. This granular access to commerce functionality is unique and expands what's possible to build with Medusa drastically.

### List of Medusa's Commerce Modules

Refer to [this reference](!resources!/commerce-modules) for a full list of commerce modules in Medusa.
Expand All @@ -33,18 +33,17 @@ Similar to your [custom modules](../modules/page.mdx), the Medusa application re
For example, consider you have a [workflow](../workflows/page.mdx) (a special function that performs a task in a series of steps with rollback mechanism) that needs a step to retrieve the total number of products. You can create a step in the workflow that resolves the Product Module's service from the container to use its methods:

export const highlights = [
["7", "Modules.PRODUCT", "Resolve the Product Module's service from the container."],
["9", "listAndCountProducts", "Use the service's method to get the products count."]
["6", `"product"`, "Resolve the Product Module's service from the container."],
["8", "listAndCountProducts", "Use the service's method to get the products count."]
]

```ts highlights={highlights}
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
import { Modules } from "@medusajs/framework/utils"

export const countProductsStep = createStep(
"count-products",
async ({ }, { container }) => {
const productModuleService = container.resolve(Modules.PRODUCT)
const productModuleService = container.resolve("product")

const [,count] = await productModuleService.listAndCountProducts()

Expand Down
13 changes: 6 additions & 7 deletions www/apps/book/app/learn/basics/events-and-subscribers/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ In this chapter, you’ll learn about Medusa's event system, and how to handle e

## Handle Core Commerce Flows with Events

When building commerce digital applications, you'll often need to perform an action after a commerce operation is performed. For example, sending an order confirmation email when the customer places an order.
When building commerce digital applications, you'll often need to perform an action after a commerce operation is performed. For example, sending an order confirmation email when the customer places an order, or syncing data that's updated in Medusa to a third-party system.

In other commerce platforms, it can be tricky to implement this when the commerce operation is performed in the platform's core. You resort to hacky workarounds or extend core services to perform your custom action, which becomes difficult to maintain in the long run and as your application grows.
Medusa emits events when core commerce features are performed, and you can listen to and handle these events in asynchronous functions. You can think of Medusa's events like you'd think about webhooks in other commerce platforms, but instead of having to setup separate applications to handle webhooks, your efforts only go into writing the logic right in your Medusa codebase.

Medusa's event system removes this complexity by emitting events when core commerce features are performed. Your efforts only go into handling these events in subscribers, which are functions executed asynchronously when an event is emitted.
You listen to an event in a subscriber, which is an asynchronous function that's executed when its associated event is emitted.

![A diagram showcasing an example of how an event is emitted when an order is placed.](https://res.cloudinary.com/dza7lstvk/image/upload/v1732277948/Medusa%20Book/order-placed-event-example_e4e4kw.jpg)

Expand All @@ -36,18 +36,17 @@ You create a subscriber in a TypeScript or JavaScript file under the `src/subscr

For example, create the file `src/subscribers/order-placed.ts` with the following content:

![Example of subscriber file in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732866244/Medusa%20Book/subscriber-dir-overview_pusyeu.jpg)

```ts title="src/subscribers/product-created.ts"
import { SubscriberArgs, type SubscriberConfig } from "@medusajs/framework"
import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
import { sendOrderConfirmationWorkflow } from "../workflows/send-order-confirmation"

export default async function orderPlacedHandler({
event: { data },
container,
}: SubscriberArgs<{ id: string }>) {
const logger = container.resolve(
ContainerRegistrationKeys.LOGGER
)
const logger = container.resolve("logger")

logger.info("Sending confirmation email...")

Expand Down
24 changes: 11 additions & 13 deletions www/apps/book/app/learn/basics/loaders/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ In this chapter, you’ll learn about loaders and how to use them.

## What is a Loader?

When building a commerce application, you'll often need to execute an action the first time the application starts. For example, if you're integrating a non-supported database such as MongoDB, you want to establish the connection when the application starts and re-use it in your customizations.
When building a commerce application, you'll often need to execute an action the first time the application starts. For example, if your application needs to connect to databases other than Medusa's PostgreSQL database, you might need to establish a connection on application startup.

In Medusa, you can execute an action when the application starts using a loader. A loader is a function exported by a [module](../modules/page.mdx), which is a package of business logic for a single domain. When the Medusa application starts, it executes all loaders exported by configured modules.

Expand All @@ -32,6 +32,8 @@ You create a loader function in a TypeScript or JavaScript file under a module's

For example, consider you have a `hello` module, you can create a loader at `src/modules/hello/loaders/hello-world.ts` with the following content:

![Example of loader file in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732865671/Medusa%20Book/loader-dir-overview_eg6vtu.jpg)

<Note title="Tip">

Learn how to create a module in [this chapter](../modules/page.mdx).
Expand All @@ -42,14 +44,11 @@ Learn how to create a module in [this chapter](../modules/page.mdx).
import {
LoaderOptions,
} from "@medusajs/framework/types"
import {
ContainerRegistrationKeys,
} from "@medusajs/framework/utils"

export default async function helloWorldLoader({
container,
}: LoaderOptions) {
const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
const logger = container.resolve("logger")

logger.info("[helloWorldLoader]: Hello, World!")
}
Expand Down Expand Up @@ -123,17 +122,16 @@ Consider your have a MongoDB module that allows you to perform operations on a M
To connect to the database, you create the following loader in your module:

export const loaderHighlights = [
["6", "ModuleOptions", "Define a type for expected options."],
["14", "ModuleOptions", "Pass the option type as a type argument to `LoaderOptions`."],
["24", "clientDb", "Create a client instance that connects to the specified database."],
["30", "register", "Register custom resource in the container."],
["31", `"mongoClient"`, "The resource's key in the container."],
["32", "asValue(clientDb)", "The resource to register."]
["5", "ModuleOptions", "Define a type for expected options."],
["13", "ModuleOptions", "Pass the option type as a type argument to `LoaderOptions`."],
["23", "clientDb", "Create a client instance that connects to the specified database."],
["29", "register", "Register custom resource in the container."],
["30", `"mongoClient"`, "The resource's key in the container."],
["31", "asValue(clientDb)", "The resource to register."]
]

```ts title="src/modules/mongo/loaders/connection.ts" highlights={loaderHighlights}
import { LoaderOptions } from "@medusajs/framework/types"
import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
import { asValue } from "awilix"
import { MongoClient } from "mongodb"

Expand All @@ -152,7 +150,7 @@ export default async function mongoConnectionLoader({
if (!options.db_name) {
throw new Error(`[MONGO MDOULE]: db_name option is required.`)
}
const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
const logger = container.resolve("logger")

try {
const clientDb = (
Expand Down
12 changes: 4 additions & 8 deletions www/apps/book/app/learn/basics/medusa-container/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ For example, consider you're creating an API route that retrieves products based
export const highlights = [
["8", "resolve", "A method that resolves resources from the container."],
[
"9",
"ContainerRegistrationKeys.QUERY",
"8",
`"query"`,
"Query's registration key in the container.",
],
]
Expand All @@ -33,9 +33,7 @@ export async function GET(
req: MedusaRequest,
res: MedusaResponse
) {
const query = req.scope.resolve(
ContainerRegistrationKeys.QUERY
)
const query = req.scope.resolve("query")

const { data: products } = await query.graph({
entity: "product",
Expand All @@ -53,8 +51,6 @@ export async function GET(

The API route accepts as a first parameter a request object that has a `scope` property, which is the Medusa container. It has a `resolve` method that resolves a resource from the container by the key it's registered with.

To resolve Query, you use the `ContainerRegistrationKeys` enum imported from `@medusajs/framework/utils`, which has the registration keys for all framework tools and resources.

<Note>

You can learn more about how Query works in [this chapter](../../advanced-development/module-links/query/page.mdx).
Expand Down Expand Up @@ -148,7 +144,7 @@ export const config = {

### Workflow Step

A [step in a workflow](../workflows/page.mdx), which is a special function where you build reliable business logic, accepts in its second parameter a `container` property, whose value is the Medusa container. Use its `resolve` method to resolve a resource by its registration key:
A [step in a workflow](../workflows/page.mdx), which is a special function where you build durable execution logic across multiple modules, accepts in its second parameter a `container` property, whose value is the Medusa container. Use its `resolve` method to resolve a resource by its registration key:

export const workflowStepsHighlight = [
["7", "container", "Receive the Medusa container as a parameter"],
Expand Down
16 changes: 10 additions & 6 deletions www/apps/book/app/learn/basics/modules/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ Modules are created in a sub-directory of `src/modules`. So, start by creating t

### 1. Create Data Model

A data model represents a table in the database. You create data models using Medusa's data modeling utility, which is built to improve readability and provide an intuitive developer experience. It simplifies defining a table's columns, relations, and indexes with straightforward methods and configurations.
A data model represents a table in the database. You create data models using Medusa's data modeling utility. It simplifies defining a table's columns, relations, and indexes with straightforward methods and configurations.

You create a data model in a TypeScript or JavaScript file under the `models` directory of a module. So, to create a `Post` data model in the Blog Module, create the file `src/modules/blog/models/post.ts` with the following content:

![Updated directory overview after adding the data model](https://res.cloudinary.com/dza7lstvk/image/upload/v1732806790/Medusa%20Book/blog-dir-overview-1_jfvovj.jpg)

```ts title="src/modules/blog/models/post.ts"
import { model } from "@medusajs/framework/utils"

Expand Down Expand Up @@ -61,12 +63,10 @@ The code snippet above defines a `Post` data model with `id` and `title` propert

You perform database operations on your data models in a service, which is a class exported by the module and acts like an interface to its functionalities. Medusa registers the service in its [container](../medusa-container/page.mdx), allowing you to resolve and use it when building custom commerce flows.

In other commerce platforms, you have to write the methods to manage each data model, such as to create or retrieve a post. This process is inefficient and wastes your time that can be spent on building custom business logic.

Medusa saves your time by generating these methods for you. Your service can extend a `MedusaService` utility, which is a function that generates a class with read and write methods for every data model in your module. Your efforts only go into building custom business logic.

You define a service in a `service.ts` or `service.js` file at the root of your module's directory. So, to create the Blog Module's service, create the file `src/modules/blog/service.ts` with the following content:

![Updated directory overview after adding the service](https://res.cloudinary.com/dza7lstvk/image/upload/v1732807230/Medusa%20Book/blog-dir-overview-2_avzb9l.jpg)

export const highlights = [
["4", "MedusaService", "The service factory function."],
["5", "MyCustom", "The data models to generate data-management methods for."]
Expand All @@ -84,7 +84,9 @@ class BlogModuleService extends MedusaService({
export default BlogModuleService
```

Your module's service extends a class returned by the `MedusaService` utility function. The `MedusaService` function accepts an object of data models, and returns a class with generated methods for data-management Create, Read, Update, and Delete (CRUD) operations on those data models. You can pass all data models in your module in this object.
Your module's service extends a class generated by the `MedusaService` utility function. This class comes with generated methods for data-management Create, Read, Update, and Delete (CRUD) operations on each of your modules, saving your time that can be spent on building custom business logic.

The `MedusaService` function accepts an object of data models to generate methods for. You can pass all data models in your module in this object.

For example, the `BlogModuleService` now has a `createPosts` method to create post records, and a `retrievePost` method to retrieve a post record. The suffix of each method (except for `retrieve`) is the pluralized name of the data model.

Expand All @@ -102,6 +104,8 @@ The final piece to a module is its definition, which is exported in an `index.ts

So, to export the definition of the Blog Module, create the file `src/modules/blog/index.ts` with the following content:

![Updated directory overview after adding the module definition](https://res.cloudinary.com/dza7lstvk/image/upload/v1732808511/Medusa%20Book/blog-dir-overview-3_dcgjaa.jpg)

export const moduleDefinitionHighlights = [
["4", "BLOG_MODULE", "Export the module's name to reference it in other customizations."],
["6", "BLOG_MODULE", "Specify the module's name."],
Expand Down
Loading

0 comments on commit b4d6a4b

Please sign in to comment.