docs: migrate guides to TSDoc references (#6100)
This commit is contained in:
@@ -0,0 +1,380 @@
|
||||
---
|
||||
displayed_sidebar: core
|
||||
slug: /development/file-service/create-file-service
|
||||
---
|
||||
|
||||
import ParameterTypes from "@site/src/components/ParameterTypes"
|
||||
|
||||
# How to Create a File Service
|
||||
|
||||
In this document, you’ll learn how to create a file service in the Medusa backend and the methods you must implement in it.
|
||||
|
||||
## Overview
|
||||
|
||||
A file service class is defined in a TypeScript or JavaScript file that’s created in the `src/services` directory.
|
||||
The class must extend the `AbstractFileService` class imported from the `@medusajs/medusa` package.
|
||||
|
||||
Based on services’ naming conventions, the file’s name should be the slug version of the file service’s name
|
||||
without `service`, and the class’s name should be the pascal case of the file service’s name following by `Service`.
|
||||
|
||||
For example, create the file `src/services/local-file.ts` with the following content:
|
||||
|
||||
```ts title="src/services/local-file.ts"
|
||||
import { AbstractFileService } from "@medusajs/medusa"
|
||||
import {
|
||||
DeleteFileType,
|
||||
FileServiceGetUploadStreamResult,
|
||||
FileServiceUploadResult,
|
||||
GetUploadedFileType,
|
||||
UploadStreamDescriptorType,
|
||||
} from "@medusajs/types"
|
||||
|
||||
class LocalFileService extends AbstractFileService {
|
||||
async upload(
|
||||
fileData: Express.Multer.File
|
||||
): Promise<FileServiceUploadResult> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async uploadProtected(
|
||||
fileData: Express.Multer.File
|
||||
): Promise<FileServiceUploadResult> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async delete(fileData: DeleteFileType): Promise<void> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async getUploadStreamDescriptor(
|
||||
fileData: UploadStreamDescriptorType
|
||||
): Promise<FileServiceGetUploadStreamResult> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async getDownloadStream(
|
||||
fileData: GetUploadedFileType
|
||||
): Promise<NodeJS.ReadableStream> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async getPresignedDownloadUrl(
|
||||
fileData: GetUploadedFileType
|
||||
): Promise<string> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
}
|
||||
|
||||
export default LocalFileService
|
||||
```
|
||||
|
||||
:::note[Multer Typing]
|
||||
|
||||
The examples implement a file service supporting local uploads.
|
||||
|
||||
If you’re using TypeScript and you're following along with the implementation,
|
||||
you should install the Multer types package in the root of your Medusa backend to resolve errors within your file service types:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install @types/multer
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## constructor
|
||||
|
||||
You can use the `constructor` of your file service to access the different services in Medusa through dependency injection.
|
||||
|
||||
You can also use the constructor to initialize your integration with the third-party provider. For example, if you use a client to connect to the third-party provider’s APIs,
|
||||
you can initialize it in the constructor and use it in other methods in the service.
|
||||
|
||||
Additionally, if you’re creating your file service as an external plugin to be installed on any Medusa backend and you want to access the options added for the plugin,
|
||||
you can access them in the constructor.
|
||||
|
||||
### Example
|
||||
|
||||
```ts
|
||||
// ...
|
||||
import { Logger } from "@medusajs/medusa"
|
||||
import * as fs from "fs"
|
||||
|
||||
class LocalFileService extends AbstractFileService {
|
||||
// can also be replaced by an environment variable
|
||||
// or a plugin option
|
||||
protected serverUrl = "http://localhost:9000"
|
||||
protected publicPath = "uploads"
|
||||
protected protectedPath = "protected-uploads"
|
||||
protected logger_: Logger
|
||||
|
||||
constructor({ logger }: InjectedDependencies) {
|
||||
// @ts-ignore
|
||||
super(...arguments)
|
||||
this.logger_ = logger
|
||||
|
||||
// for public uploads
|
||||
if (!fs.existsSync(this.publicPath)) {
|
||||
fs.mkdirSync(this.publicPath)
|
||||
}
|
||||
|
||||
// for protected uploads
|
||||
if (!fs.existsSync(this.protectedPath)) {
|
||||
fs.mkdirSync(this.protectedPath)
|
||||
}
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
<ParameterTypes parameters={[{"name":"container","type":"[MedusaContainer](../../medusa/types/medusa.MedusaContainer-2.mdx)","description":"An instance of `MedusaContainer` that allows you to access other resources, such as services, in your Medusa backend.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"config","type":"`Record<string, unknown>`","description":"If this file service is created in a plugin, the plugin's options are passed in this parameter.","optional":true,"defaultValue":"","expandable":false,"children":[]}]} />
|
||||
|
||||
___
|
||||
|
||||
## Methods
|
||||
|
||||
### upload
|
||||
|
||||
This method is used to upload a file to the Medusa backend.
|
||||
|
||||
#### Example
|
||||
|
||||
```ts
|
||||
class LocalFileService extends AbstractFileService {
|
||||
// ...
|
||||
async upload(
|
||||
fileData: Express.Multer.File
|
||||
): Promise<FileServiceUploadResult> {
|
||||
const filePath =
|
||||
`${this.publicPath}/${fileData.originalname}`
|
||||
fs.copyFileSync(fileData.path, filePath)
|
||||
return {
|
||||
url: `${this.serverUrl}/${filePath}`,
|
||||
key: filePath,
|
||||
}
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
:::tip
|
||||
|
||||
This example does not account for duplicate names to maintain simplicity in this guide. So, an uploaded file can replace another existing file that has the same name.
|
||||
|
||||
:::
|
||||
|
||||
#### Parameters
|
||||
|
||||
<ParameterTypes parameters={[{"name":"fileData","type":"`File`","description":"A [multer file object](http://expressjs.com/en/resources/middleware/multer.html#file-information).\nThe file is uploaded to a temporary directory by default. Among the file’s details, you can access the file’s path in the `path` property of the file object.","optional":false,"defaultValue":"","expandable":false,"children":[]}]} />
|
||||
|
||||
#### Returns
|
||||
|
||||
<ParameterTypes parameters={[{"name":"Promise","type":"Promise<[FileServiceUploadResult](../../medusa/interfaces/medusa.FileServiceUploadResult.mdx)>","optional":false,"defaultValue":"","description":"The details of the upload's result.","expandable":false,"children":[{"name":"FileServiceUploadResult","type":"[FileServiceUploadResult](../../medusa/interfaces/medusa.FileServiceUploadResult.mdx)","optional":false,"defaultValue":"","description":"Details of a file upload's result.","expandable":false,"children":[{"name":"url","type":"`string`","description":"The file's URL.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"key","type":"`string`","description":"The file's key. This key is used in other operations,\nsuch as deleting a file.","optional":false,"defaultValue":"","expandable":false,"children":[]}]}]}]} />
|
||||
|
||||
### uploadProtected
|
||||
|
||||
This method is used to upload a file to the Medusa backend, but to a protected storage. Typically, this would be used to store files that
|
||||
shouldn’t be accessible by using the file’s URL or should only be accessible by authenticated users. For example, exported or imported
|
||||
CSV files.
|
||||
|
||||
#### Example
|
||||
|
||||
```ts
|
||||
class LocalFileService extends AbstractFileService {
|
||||
// ...
|
||||
async uploadProtected(
|
||||
fileData: Express.Multer.File
|
||||
): Promise<FileServiceUploadResult> {
|
||||
const filePath =
|
||||
`${this.protectedPath}/${fileData.originalname}`
|
||||
fs.copyFileSync(fileData.path, filePath)
|
||||
return {
|
||||
url: `${this.serverUrl}/${filePath}`,
|
||||
key: filePath
|
||||
}
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
:::tip
|
||||
|
||||
This example does not account for duplicate names to maintain simplicity in this guide. So, an uploaded file can replace another existing file that has the same name.
|
||||
|
||||
:::
|
||||
|
||||
#### Parameters
|
||||
|
||||
<ParameterTypes parameters={[{"name":"fileData","type":"`File`","description":"A [multer file object](http://expressjs.com/en/resources/middleware/multer.html#file-information).\nThe file is uploaded to a temporary directory by default. Among the file’s details, you can access the file’s path in the `path` property of the file object.","optional":false,"defaultValue":"","expandable":false,"children":[]}]} />
|
||||
|
||||
#### Returns
|
||||
|
||||
<ParameterTypes parameters={[{"name":"Promise","type":"Promise<[FileServiceUploadResult](../../medusa/interfaces/medusa.FileServiceUploadResult.mdx)>","optional":false,"defaultValue":"","description":"The details of the upload's result.","expandable":false,"children":[{"name":"FileServiceUploadResult","type":"[FileServiceUploadResult](../../medusa/interfaces/medusa.FileServiceUploadResult.mdx)","optional":false,"defaultValue":"","description":"Details of a file upload's result.","expandable":false,"children":[{"name":"url","type":"`string`","description":"The file's URL.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"key","type":"`string`","description":"The file's key. This key is used in other operations,\nsuch as deleting a file.","optional":false,"defaultValue":"","expandable":false,"children":[]}]}]}]} />
|
||||
|
||||
### delete
|
||||
|
||||
This method is used to delete a file from storage.
|
||||
|
||||
#### Example
|
||||
|
||||
```ts
|
||||
class LocalFileService extends AbstractFileService {
|
||||
|
||||
async delete(
|
||||
fileData: DeleteFileType
|
||||
): Promise<void> {
|
||||
fs.rmSync(fileData.fileKey)
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
<ParameterTypes parameters={[{"name":"fileData","type":"[DeleteFileType](../../medusa/interfaces/medusa.DeleteFileType.mdx)","description":"The details of the file to remove.","optional":false,"defaultValue":"","expandable":false,"children":[{"name":"fileKey","type":"`string`","description":"The file's key. When uploading a file, the\nreturned key is used here.","optional":false,"defaultValue":"","expandable":false,"children":[]}]}]} />
|
||||
|
||||
#### Returns
|
||||
|
||||
<ParameterTypes parameters={[{"name":"Promise","type":"Promise<void>","optional":false,"defaultValue":"","description":"Resolves when the file is deleted successfully.","expandable":false,"children":[]}]} />
|
||||
|
||||
### getUploadStreamDescriptor
|
||||
|
||||
This method is used to retrieve a write stream to be used to upload a file.
|
||||
|
||||
#### Example
|
||||
|
||||
```ts
|
||||
// ...
|
||||
import { Stream } from "stream"
|
||||
|
||||
class LocalFileService extends AbstractFileService {
|
||||
// ...
|
||||
async getUploadStreamDescriptor({
|
||||
name,
|
||||
ext,
|
||||
isPrivate = true,
|
||||
}: UploadStreamDescriptorType
|
||||
): Promise<FileServiceGetUploadStreamResult> {
|
||||
const filePath = `${isPrivate ?
|
||||
this.publicPath : this.protectedPath
|
||||
}/${name}.${ext}`
|
||||
|
||||
const pass = new Stream.PassThrough()
|
||||
const writeStream = fs.createWriteStream(filePath)
|
||||
|
||||
pass.pipe(writeStream)
|
||||
|
||||
return {
|
||||
writeStream: pass,
|
||||
promise: Promise.resolve(),
|
||||
url: `${this.serverUrl}/${filePath}`,
|
||||
fileKey: filePath,
|
||||
}
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
<ParameterTypes parameters={[{"name":"fileData","type":"[UploadStreamDescriptorType](../../medusa/interfaces/medusa.UploadStreamDescriptorType.mdx)","description":"The details of the file being uploaded.","optional":false,"defaultValue":"","expandable":false,"children":[{"name":"name","type":"`string`","description":"The name of the file.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"ext","type":"`string`","description":"The extension of the file.","optional":true,"defaultValue":"","expandable":false,"children":[]},{"name":"isPrivate","type":"`boolean`","description":"Whether the file should be uploaded to a private bucket or location. By convention, the default value of this property is `true`.","optional":true,"defaultValue":"","expandable":false,"children":[]}]}]} />
|
||||
|
||||
#### Returns
|
||||
|
||||
<ParameterTypes parameters={[{"name":"Promise","type":"Promise<[FileServiceGetUploadStreamResult](../../medusa/interfaces/medusa.FileServiceGetUploadStreamResult.mdx)>","optional":false,"defaultValue":"","description":"The result of the file-stream upload.","expandable":false,"children":[{"name":"FileServiceGetUploadStreamResult","type":"[FileServiceGetUploadStreamResult](../../medusa/interfaces/medusa.FileServiceGetUploadStreamResult.mdx)","optional":false,"defaultValue":"","description":"The relevant details to upload a file through a stream.","expandable":false,"children":[{"name":"writeStream","type":"`PassThrough`","description":"A [PassThrough](https://nodejs.org/api/stream.html#class-streampassthrough) write stream object to be used to write the file.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"promise","type":"Promise<any>","description":"A promise that should resolved when the writing process is done to finish the upload.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"url","type":"`string`","description":"The URL of the file once it’s uploaded.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"fileKey","type":"`string`","description":"The identifier of the file in the storage. For example, for a local file service, this can be the file's name.","optional":false,"defaultValue":"","expandable":false,"children":[]}]}]}]} />
|
||||
|
||||
### getDownloadStream
|
||||
|
||||
This method is used to retrieve a read stream for a file, which can then be used to download the file.
|
||||
|
||||
#### Example
|
||||
|
||||
```ts
|
||||
class LocalFileService extends AbstractFileService {
|
||||
|
||||
async getDownloadStream({
|
||||
fileKey,
|
||||
isPrivate = true,
|
||||
}: GetUploadedFileType
|
||||
): Promise<NodeJS.ReadableStream> {
|
||||
const filePath = `${isPrivate ?
|
||||
this.publicPath : this.protectedPath
|
||||
}/${fileKey}`
|
||||
const readStream = fs.createReadStream(filePath)
|
||||
|
||||
return readStream
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
<ParameterTypes parameters={[{"name":"fileData","type":"[GetUploadedFileType](../../medusa/interfaces/medusa.GetUploadedFileType.mdx)","description":"The details of the file.","optional":false,"defaultValue":"","expandable":false,"children":[{"name":"fileKey","type":"`string`","description":"The file's key.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"isPrivate","type":"`boolean`","description":"Whether the file is private.","optional":true,"defaultValue":"","expandable":false,"children":[]}]}]} />
|
||||
|
||||
#### Returns
|
||||
|
||||
<ParameterTypes parameters={[{"name":"Promise","type":"Promise<ReadableStream>","optional":false,"defaultValue":"","description":"The [read stream](https://nodejs.org/api/webstreams.html#class-readablestream) to read and download the file.","expandable":false,"children":[{"name":"ReadableStream","type":"`ReadableStream`","optional":false,"defaultValue":"","description":"","expandable":false,"children":[]}]}]} />
|
||||
|
||||
### getPresignedDownloadUrl
|
||||
|
||||
This method is used to retrieve a download URL of the file. For some file services, such as S3, a presigned URL indicates a temporary URL to get access to a file.
|
||||
|
||||
If your file service doesn’t perform or offer a similar functionality, you can just return the URL to download the file.
|
||||
|
||||
#### Example
|
||||
|
||||
```ts
|
||||
class LocalFileService extends AbstractFileService {
|
||||
|
||||
async getPresignedDownloadUrl({
|
||||
fileKey,
|
||||
isPrivate = true,
|
||||
}: GetUploadedFileType
|
||||
): Promise<string> {
|
||||
// Local upload doesn't provide
|
||||
// support for presigned URLs,
|
||||
// so just return the file's URL.
|
||||
|
||||
const filePath = `${isPrivate ?
|
||||
this.publicPath : this.protectedPath
|
||||
}/${fileKey}`
|
||||
return `${this.serverUrl}/${filePath}`
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
<ParameterTypes parameters={[{"name":"fileData","type":"[GetUploadedFileType](../../medusa/interfaces/medusa.GetUploadedFileType.mdx)","description":"The details of the file.","optional":false,"defaultValue":"","expandable":false,"children":[{"name":"fileKey","type":"`string`","description":"The file's key.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"isPrivate","type":"`boolean`","description":"Whether the file is private.","optional":true,"defaultValue":"","expandable":false,"children":[]}]}]} />
|
||||
|
||||
#### Returns
|
||||
|
||||
<ParameterTypes parameters={[{"name":"Promise","type":"Promise<string>","optional":false,"defaultValue":"","description":"The presigned URL to download the file","expandable":false,"children":[{"name":"string","type":"`string`","optional":false,"defaultValue":"","description":"","expandable":false,"children":[]}]}]} />
|
||||
|
||||
---
|
||||
|
||||
## Test Implementation
|
||||
|
||||
:::note
|
||||
|
||||
If you created your file service in a plugin, refer to [this guide on how to test plugins](https://docs.medusajs.com/development/plugins/create#test-your-plugin).
|
||||
|
||||
:::
|
||||
|
||||
After finishing your file service implementation:
|
||||
|
||||
1\. Run the `build` command in the root of your Medusa backend:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
2\. Start the backend with the `develop` command:
|
||||
|
||||
```bash
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
3\. Upload a file using the [Admin REST APIs](https://docs.medusajs.com/api/admin#uploads_postuploads) or using the Medusa admin, for example, to [upload a product's thumbnail](https://docs.medusajs.com/user-guide/products/manage#manage-thumbnails).
|
||||
Reference in New Issue
Block a user