Skip to content

feat: add OPTIONS server method #8731

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Feb 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/gentle-toes-count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': minor
---

feat: add `OPTIONS` server method
8 changes: 5 additions & 3 deletions documentation/docs/20-core-concepts/10-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ Like `+layout.js`, `+layout.server.js` can export [page options](page-options)

## +server

As well as pages, you can define routes with a `+server.js` file (sometimes referred to as an 'API route' or an 'endpoint'), which gives you full control over the response. Your `+server.js` file (or `+server.ts`) exports functions corresponding to HTTP verbs like `GET`, `POST`, `PATCH`, `PUT` and `DELETE` that take a `RequestEvent` argument and return a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) object.
As well as pages, you can define routes with a `+server.js` file (sometimes referred to as an 'API route' or an 'endpoint'), which gives you full control over the response. Your `+server.js` file (or `+server.ts`) exports functions corresponding to HTTP verbs like `GET`, `POST`, `PATCH`, `PUT`, `DELETE`, and `OPTIONS` that take a `RequestEvent` argument and return a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) object.

For example we could create an `/api/random-number` route with a `GET` handler:

Expand Down Expand Up @@ -279,9 +279,11 @@ You can use the [`error`](modules#sveltejs-kit-error), [`redirect`](modules#svel

If an error is thrown (either `throw error(...)` or an unexpected error), the response will be a JSON representation of the error or a fallback error page — which can be customised via `src/error.html` — depending on the `Accept` header. The [`+error.svelte`](#error) component will _not_ be rendered in this case. You can read more about error handling [here](errors).

> When creating an `OPTIONS` handler, note that Vite will inject `Access-Control-Allow-Origin` and `Access-Control-Allow-Methods` headers — these will not be present in production unless you add them.

### Receiving data

By exporting `POST`/`PUT`/`PATCH`/`DELETE` handlers, `+server.js` files can be used to create a complete API:
By exporting `POST`/`PUT`/`PATCH`/`DELETE`/`OPTIONS` handlers, `+server.js` files can be used to create a complete API:

```svelte
/// file: src/routes/add/+page.svelte
Expand Down Expand Up @@ -327,7 +329,7 @@ export async function POST({ request }) {

`+server.js` files can be placed in the same directory as `+page` files, allowing the same route to be either a page or an API endpoint. To determine which, SvelteKit applies the following rules:

- `PUT`/`PATCH`/`DELETE` requests are always handled by `+server.js` since they do not apply to pages
- `PUT`/`PATCH`/`DELETE`/`OPTIONS` requests are always handled by `+server.js` since they do not apply to pages
- `GET`/`POST` requests are treated as page requests if the `accept` header prioritises `text/html` (in other words, it's a browser page request), else they are handled by `+server.js`

## $types
Expand Down
1 change: 1 addition & 0 deletions packages/kit/src/core/postbuild/analyse.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ async function analyse({ manifest_path, env }) {
if (mod.PUT) methods.add('PUT');
if (mod.PATCH) methods.add('PATCH');
if (mod.DELETE) methods.add('DELETE');
if (mod.OPTIONS) methods.add('OPTIONS');

config = mod.config;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/exports/vite/build/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export function assets_base(config) {
return (config.paths.assets || config.paths.base || '.') + '/';
}

const method_names = new Set(['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH']);
const method_names = new Set(['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH', 'OPTIONS']);

// If we'd written this in TypeScript, it could be easy...
/**
Expand Down
6 changes: 5 additions & 1 deletion packages/kit/src/exports/vite/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,11 @@ function kit({ svelte_config }) {
// Ignore all siblings of config.kit.outDir/generated
`${posixify(kit.outDir)}/!(generated)`
]
}
},
cors: { preflightContinue: true }
},
preview: {
cors: { preflightContinue: true }
},
optimizeDeps: {
exclude: [
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/runtime/server/endpoint.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export async function render_endpoint(event, route, mod, state) {
export function is_endpoint_request(event) {
const { method, headers } = event.request;

if (method === 'PUT' || method === 'PATCH' || method === 'DELETE') {
if (method === 'PUT' || method === 'PATCH' || method === 'DELETE' || method === 'OPTIONS') {
// These methods exist exclusively for endpoints
return true;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/runtime/server/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function method_not_allowed(mod, method) {
export function allowed_methods(mod) {
const allowed = [];

for (const method of ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']) {
for (const method in ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']) {
if (method in mod) allowed.push(method);
}

Expand Down
1 change: 1 addition & 0 deletions packages/kit/src/utils/exports.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const validate_server_exports = validator([
'PATCH',
'PUT',
'DELETE',
'OPTIONS',
'prerender',
'trailingSlash',
'config'
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/utils/exports.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ test('validates +server.js', () => {
validate_server_exports({
answer: 42
});
}, /Invalid export 'answer' \(valid exports are GET, POST, PATCH, PUT, DELETE, prerender, trailingSlash, config, or anything with a '_' prefix\)/);
}, /Invalid export 'answer' \(valid exports are GET, POST, PATCH, PUT, DELETE, OPTIONS, prerender, trailingSlash, config, or anything with a '_' prefix\)/);
});

test.run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** @type {import('@sveltejs/kit').RequestHandler} */
export function OPTIONS() {
return new Response('ok');
}
11 changes: 11 additions & 0 deletions packages/kit/test/apps/basics/test/server.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,17 @@ test.describe('Endpoints', () => {
const response = await request.put('/endpoint-input/sha256', { data });
expect(await response.text()).toEqual(digest);
});

test('OPTIONS handler', async ({ request }) => {
const url = '/endpoint-output/options';

var response = await request.fetch(url, {
method: 'OPTIONS'
});

expect(response.status()).toBe(200);
expect(await response.text()).toBe('ok');
});
});

test.describe('Errors', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/types/private.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export interface CspDirectives {
>;
}

export type HttpMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
export type HttpMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS';

export interface Logger {
(msg: string): void;
Expand Down