Skip to content

Commit

Permalink
feat: add upload functionality in the middleware (#7323)
Browse files Browse the repository at this point in the history
* feat: add upload functionality in the middleware
  • Loading branch information
bartoszherba authored Nov 22, 2024
1 parent 3fcf43c commit f02f7cf
Show file tree
Hide file tree
Showing 23 changed files with 694 additions and 55 deletions.
23 changes: 23 additions & 0 deletions .changeset/hot-maps-unite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
"@vue-storefront/middleware": minor
---

**[ADDED]** Support for file uploads
Now you can upload files to the server with a `multipart/form-data` content type. Files are available in the `req.files` object.

```ts
// Example of an endpoint that handles file uploads
export const upload = (context) => {
// Files are available in the `req.files` object
const { files } = context.req;

// Do something with files

return Promise.resolve({
status: 200,
message: "ok",
});
};
```

Please, read the [Getting Started guide](https://docs.alokai.com/middleware/guides/getting-started#file-upload-configuration) for more information about file uploads.
2 changes: 2 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@ module.exports = {
extends: "@vue-storefront/eslint-config-integrations",
rules: {
"class-methods-use-this": "off",
"@typescript-eslint/no-restricted-types": "off",
"@typescript-eslint/ban-types": "off",
},
};
146 changes: 129 additions & 17 deletions docs/content/3.middleware/2.guides/2.getting-started.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Installation

If you're building your Alokai application from scratch, you'll need to set up the middleware to connect to your backend services.
If you're building your Alokai application from scratch, you'll need to set up the middleware to connect to your backend services.

## Creating the Application

Since the middleware is a separate app, it should be built outside of your frontend Alokai application. We recommend using a monorepo to keep both applications in a single repository.
Since the middleware is a separate app, it should be built outside of your frontend Alokai application. We recommend using a monorepo to keep both applications in a single repository.

Our storefronts use an `apps/server` to store the middleware application, so most examples in our documentation will use this folder structure.

Expand All @@ -19,34 +19,37 @@ apps/
To start, you need to install the `@vue-storefront/middleware` package. It contains the core functionality of the middleware that you can extend with integrations.

::code-group

```sh[npm]
npm i @vue-storefront/middleware consola ts-node-dev
```

```sh[yarn]
yarn add @vue-storefront/middleware consola ts-node-dev
```

```sh[pnpm]
pnpm i @vue-storefront/middleware consola ts-node-dev
```

::

If you just made a new folder for your middleware, this command will also create a `package.json` file to your project's root directory.
If you just made a new folder for your middleware, this command will also create a `package.json` file to your project's root directory.

## Running the Application

The `@vue-storefront/middleware` package exposes a `createServer` function that you can use to initialize the Express application that runs the Alokai middleware.

The `createServer` function accepts an `integrations` object and returns an Express.js application that can be used to listen on a port.


```ts [src/index.ts]
import { createServer } from '@vue-storefront/middleware';
import consola from 'consola';
import config from '../middleware.config';
import { createServer } from "@vue-storefront/middleware";
import consola from "consola";
import config from "../middleware.config";

(async () => {
const app = await createServer({ integrations: config.integrations });
const host = process.argv[2] ?? '0.0.0.0';
const host = process.argv[2] ?? "0.0.0.0";
const port = Number(process.argv[3]) || 4000;

app.listen(port, host, () => {
Expand All @@ -65,19 +68,130 @@ With our middleware file set up, we can use `ts-node-dev` to run our application
}
```

## Configuration Options

The `createServer` function accepts a second parameter for configuring various middleware options:

```ts [src/index.ts]
const app = await createServer(
{ integrations: config.integrations },
{
// CORS configuration
cors: {
origin: "http://localhost:3000",
credentials: true,
},
// Body parser configuration
bodyParser: {
limit: "50mb",
},
// Cookie parser configuration
cookieParser: {
secret: "secret",
},
// File upload configuration
fileUpload: {
enabled: true, // Enable/disable file upload functionality
maxFileSize: 5242880, // Maximum file size in bytes (default: 5MB)
maxFiles: 5, // Maximum number of files per upload
allowedMimeTypes: ["image/*", "application/pdf"], // Allowed file types
fieldNames: [], // Accepted form field names for file uploads
},
}
);
```

### CORS Configuration

Configure Cross-Origin Resource Sharing (CORS) settings. By default, `http://localhost:4000` is included in the allowed origins.

### Body Parser Configuration

Configure the body-parser middleware settings for handling request bodies.

### Cookie Parser Configuration

Configure the cookie-parser middleware settings for handling cookies.

### File Upload Configuration

Configure file upload handling for `multipart/form-data` requests. You can either provide static options or a function that returns configuration based on the request:

```ts
fileUpload: (req) => ({
enabled: req.headers["x-enable-upload"] === "true",
maxFileSize: 5242880,
maxFiles: 5,
allowedMimeTypes: ["image/*", "application/pdf"],
fieldNames: [],
});
```

Available options:

- `enabled`: Enable/disable file upload functionality (default: `true`)
- `maxFileSize`: Maximum file size in bytes (default: 5MB) // Maximum file size is limited to 10MB
- `maxFiles`: Maximum number of files per upload (default: 5)
- `allowedMimeTypes`: Array of allowed MIME types (default: `["image/*", "application/pdf"]`)
- `fieldNames`: Array of accepted form field names for file uploads (default: `[]`)

When file uploads are enabled, uploaded files are available in the `req.files` object within your API endpoints:

```ts
export const upload = (context) => {
const { files } = context.req;
// Handle uploaded files
return Promise.resolve({
status: 200,
message: "ok",
});
};
```

You can also dynamically control file upload behavior on a per-request basis. This is particularly useful when you want to enable uploads only for specific scenarios, such as:

- Authenticated requests
- Requests with specific headers
- Requests from certain origins
- Different file size limits for different endpoints

Here's an example of dynamic configuration based on request headers:

```ts [src/index.ts]
const app = await createServer(
{ integrations: config.integrations },
{
fileUpload: (req) => ({
enabled: req.headers["x-enable-upload"] === "true",
maxFileSize: req.headers["x-upload-size"]
? parseInt(req.headers["x-upload-size"])
: 5242880,
maxFiles: 5,
allowedMimeTypes: ["image/*", "application/pdf"],
fieldNames: [],
}),
}
);
```

In this example:

- File uploads are only enabled when the `x-enable-upload: true` header is present
- The maximum file size can be controlled via the `x-upload-size` header
- Other options remain static but could also be made dynamic based on your needs

## Adding Integrations

Integrations contain code extend the middleware with additional functionality to make it easy to work with different third-party services.
Integrations contain code extend the middleware with additional functionality to make it easy to work with different third-party services.

Alokai has available integrations that you can use out of the box, but you can also create your own integrations to connect to any service that you need.

Most integrations are made up of two parts:

1. An SDK module that extends the Alokai SDK to add methods to your frontend
2. An API Client that extends the middleware to add new API endpoints or modify the Express.js application itself

We recommend creating a `middleware.config.js` file located at the project's root that you can use to configure API Clients for your integrations.
We recommend creating a `middleware.config.js` file located at the project's root that you can use to configure API Clients for your integrations.

Each integration will be an object with a unique key that you can find in each integration's installation guide. This key is used for communication with the middleware, so changing it might cause the integration to break.

Expand All @@ -91,17 +205,17 @@ The object for each integration can contain the following properties:
```ts [middleware.config.ts]
export const integrations = {
example: {
location: '@vue-storefront/example-api/server',
location: "@vue-storefront/example-api/server",
configuration: {
// configuration for the integration (see the integration's installation guide for details)
},
extensions: (baseExtensions) => [
...baseExtensions,
// your additional extensions
],
customQueries: {}
}
}
customQueries: {},
},
};
```

## Local Development
Expand All @@ -112,7 +226,7 @@ If you want to have the same `tsconfig.json` options as our boilerplate, you can

### Using `nodemon`

To make local development smoother, you can use [`nodemon`](https://www.npmjs.com/package/nodemon) to watch for changes in your middleware application and restart the server automatically.
To make local development smoother, you can use [`nodemon`](https://www.npmjs.com/package/nodemon) to watch for changes in your middleware application and restart the server automatically.

```sh
npm i -D nodemon
Expand All @@ -126,7 +240,6 @@ Finally, we can create a `nodemon.json` file to set the files to watch and the c
"ext": "ts",
"exec": "ts-node-dev src/index.ts"
}

```

Then, you can add a script to your `package.json` file to run the middleware with `nodemon`.
Expand All @@ -142,4 +255,3 @@ Then, you can add a script to your `package.json` file to run the middleware wit
## Next Steps

:card{to="/middleware/guides/extensions" title="Creating Extensions" description="Use extensions to customize or extend the middleware to match your needs." icon="gridicons:customize"}

Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export { error } from "./error";
export { throwAxiosError } from "./throwAxiosError";
export { throwError } from "./throwError";
export { getConfig } from "./getConfig";
export { upload } from "./upload";
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const upload = (context) => {
const { files } = context.req;

return Promise.resolve({
status: 200,
message: "ok",
error: false,
files,
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { apiClientFactory } from "../../../src/apiClientFactory";
import * as api from "./api";

const onCreate = async (config: Record<string, unknown> = {}) => {
return {
config,
client: null,
};
};

const { createApiClient } = apiClientFactory({
onCreate,
api,
});

export { createApiClient };
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const testingExtension = {
]);
},
},
hooks(req, res, alokai) {
hooks(_req, _res, alokai) {
const logger = getLogger(alokai);
logger.info("hooks");
return {
Expand Down
Loading

0 comments on commit f02f7cf

Please sign in to comment.