refactor: a better and more stable way to specify baseURL and browserBaseURL options

BREAKING CHANGE: prefix should be set to `/api` for backward compability. refer to new docs.
master
Pooya Parsa 2018-01-16 20:45:12 +03:30
parent 8e0ee32757
commit 533cf4ee2c
5 changed files with 158 additions and 98 deletions

177
README.md
View File

@ -32,47 +32,50 @@
# Table of Contents # Table of Contents
- [Features](#features) * [Features](#features)
- [Setup](#setup) * [Setup](#setup)
- [Usage](#usage) * [Usage](#usage)
- [Component](#component) * [Component](#component)
- [Store](#store-nuxtserverinit) * [Store](#store-nuxtserverinit)
- [Store Actions](#store-actions) * [Store Actions](#store-actions)
- [Options](#options) * [Options](#options)
- [browserBaseURL](#browserbaseurl) * [baseURL](#baseURL)
- [credentials](#credentials) * [browserBaseURL](#browserbaseurl)
- [debug](#debug) * [credentials](#credentials)
- [proxyHeaders](#proxyheaders) * [debug](#debug)
- [proxyHeadersIgnore](#proxyheadersignore) * [proxyHeaders](#proxyheaders)
- [redirectError](#redirecterror) * [proxyHeadersIgnore](#proxyheadersignore)
- [requestInterceptor](#requestinterceptor) * [redirectError](#redirecterror)
- [responseInterceptor](#responseinterceptor) * [requestInterceptor](#requestinterceptor)
- [init](#init) * [responseInterceptor](#responseinterceptor)
- [disableDefaultErrorHandler](#disabledefaulterrorhandler) * [init](#init)
- [errorHandler](#errorhandler) * [disableDefaultErrorHandler](#disabledefaulterrorhandler)
- [Helpers](#helpers) * [errorHandler](#errorhandler)
- [Fetch Style Requests](#fetch-style-requests) * [Helpers](#helpers)
- [Set Header](#setheadername-value-scopescommon) * [Fetch Style Requests](#fetch-style-requests)
- [Set Token](#settokentoken-type-scopescommon) * [Set Header](#setheadername-value-scopescommon)
- [Dynamic API Backend](#dynamic-api-backend) * [Set Token](#settokentoken-type-scopescommon)
* [Dynamic API Backend](#dynamic-api-backend)
## Features ## Features
- Automatically set base URL for client & server side * Automatically set base URL for client & server side
- Exposes `setToken` function to `$axios` so we can easily and globally set authentication tokens * Exposes `setToken` function to `$axios` so we can easily and globally set authentication tokens
- Throws *nuxt-friendly* errors and optionally redirect on specific error codes * Throws _nuxt-friendly_ errors and optionally redirect on specific error codes
- Automatically enables `withCredentials` when requesting to base URL * Automatically enables `withCredentials` when requesting to base URL
- Proxy request headers in SSR (Useful for auth) * Proxy request headers in SSR (Useful for auth)
- Fetch Style requests * Fetch Style requests
## Setup ## Setup
Install with npm: Install with npm:
```bash ```bash
>_ npm install @nuxtjs/axios >_ npm install @nuxtjs/axios
``` ```
Install with yarn: Install with yarn:
```bash ```bash
>_ yarn add @nuxtjs/axios >_ yarn add @nuxtjs/axios
``` ```
@ -93,7 +96,7 @@ Install with yarn:
## Usage ## Usage
### Component ### Component
**`asyncData`** **`asyncData`**
@ -116,6 +119,7 @@ methods: {
``` ```
### Store `nuxtServerInit` ### Store `nuxtServerInit`
```js ```js
async nuxtServerInit ({ commit }, { app }) { async nuxtServerInit ({ commit }, { app }) {
const ip = await app.$axios.$get('http://icanhazip.com') const ip = await app.$axios.$get('http://icanhazip.com')
@ -124,6 +128,7 @@ async nuxtServerInit ({ commit }, { app }) {
``` ```
### Store actions ### Store actions
(Needs Nuxt >= 1.0.0-RC8) (Needs Nuxt >= 1.0.0-RC8)
```js ```js
@ -139,37 +144,49 @@ async nuxtServerInit ({ commit }, { app }) {
``` ```
## Options ## Options
You can pass options using module options or `axios` section in `nuxt.config.js`
You can pass options using module options or `axios` section in `nuxt.config.js`
### `prefix`, `host` and `port`
This options are used for default values of `baseURL` and `browserBaseURL`.
Can be customized with `API_PREFIX`, `API_HOST` (or `HOST`) and `API_PORT` (or `PORT`) environment variables too.
Default value of `prefix` is `/`.
### `baseURL` ### `baseURL`
- Default: `http://[HOST]:[PORT]/api`
Base URL is required for requests in server-side & SSR and prepended to all requests with relative path. * Default: `http://[HOST]:[PORT][PREFIX]`
You can also use environment variable `API_URL` which **overrides** `baseURL`.
Base URL is required for requests in server-side & SSR and prepended to all axios requests.
Environment variable `API_URL` can be used to **override** `baseURL`.
### `browserBaseURL` ### `browserBaseURL`
- Default: `/api`
Base URL which is used in client side prepended to all requests with relative path. * Default: `baseURL` (or `prefix` when `options.proxyMode` is `true`)
You can also use environment variable `API_URL_BROWSER` which **overrides** `browserBaseURL`.
- If `browserBaseURL` is not provided it defaults to `baseURL` value. Base URL which is used in client side and prepended to all axios requests.
- If hostname & port of `browserbaseURL` are equal to nuxt server, it defaults to relative part of `baseURL`.
So if your nuxt application is being accessed under a different domain, requests go to same origin and prevents Cross-Origin problems. Environment variable `API_URL_BROWSER` can be used to **override** `browserBaseURL`.
### `credentials` ### `credentials`
- Default: `true`
* Default: `true`
Adds an interceptor to automatically set `withCredentials` config of axios when requesting to `baseUrl` Adds an interceptor to automatically set `withCredentials` config of axios when requesting to `baseUrl`
which allows passing authentication headers to backend. which allows passing authentication headers to backend.
### `debug` ### `debug`
- Default: `false`
* Default: `false`
Adds interceptors to log all responses and requests Adds interceptors to log all responses and requests
### `proxyHeaders` ### `proxyHeaders`
- Default: `true`
* Default: `true`
In SSR context, sets client request header as axios default request headers. In SSR context, sets client request header as axios default request headers.
This is useful for making requests which need cookie based auth on server side. This is useful for making requests which need cookie based auth on server side.
@ -178,15 +195,18 @@ Also helps making consistent requests in both SSR and Client Side code.
> **NOTE:** If directing requests at a url protected by CloudFlare's CDN you should set this to false to prevent CloudFlare from mistakenly detecting a reverse proxy loop and returning a 403 error. > **NOTE:** If directing requests at a url protected by CloudFlare's CDN you should set this to false to prevent CloudFlare from mistakenly detecting a reverse proxy loop and returning a 403 error.
### `proxyHeadersIgnore` ### `proxyHeadersIgnore`
- Default `['host', 'accept']`
* Default `['host', 'accept']`
Only efficient when `proxyHeaders` is set to true. Removes unwanted request headers to the API backend in SSR. Only efficient when `proxyHeaders` is set to true. Removes unwanted request headers to the API backend in SSR.
### `redirectError` ### `redirectError`
- Default: `{}`
* Default: `{}`
This option is a map from specific error codes to page which they should be redirect. This option is a map from specific error codes to page which they should be redirect.
For example if you want redirecting all `401` errors to `/login` use: For example if you want redirecting all `401` errors to `/login` use:
```js ```js
axios: { axios: {
redirectError: { redirectError: {
@ -196,7 +216,8 @@ axios: {
``` ```
### `requestInterceptor` ### `requestInterceptor`
- Default: `null`
* Default: `null`
Function for manipulating axios requests. Useful for setting custom headers, Function for manipulating axios requests. Useful for setting custom headers,
for example based on the store state. The second argument is the nuxt context. for example based on the store state. The second argument is the nuxt context.
@ -211,7 +232,8 @@ requestInterceptor: (config, { store }) => {
``` ```
### `responseInterceptor` ### `responseInterceptor`
- Default: `null`
* Default: `null`
```js ```js
responseInterceptor: (response, ctx) => { responseInterceptor: (response, ctx) => {
@ -219,11 +241,11 @@ responseInterceptor: (response, ctx) => {
} }
``` ```
Function for manipulating axios responses. Function for manipulating axios responses.
### `init` ### `init`
- Default: `null`
* Default: `null`
Function `init(axios, ctx)` to do additional things with axios. Example: Function `init(axios, ctx)` to do additional things with axios. Example:
@ -236,13 +258,15 @@ axios: {
``` ```
### `disableDefaultErrorHandler` ### `disableDefaultErrorHandler`
- Default: `false`
* Default: `false`
If you want to disable the default error handler for some reason, you can do it so If you want to disable the default error handler for some reason, you can do it so
by setting the option `disableDefaultErrorHandler` to true. by setting the option `disableDefaultErrorHandler` to true.
### `errorHandler` ### `errorHandler`
- Default: (Return promise rejection with error)
* Default: (Return promise rejection with error)
Function for custom global error handler. Function for custom global error handler.
This example uses nuxt default error page. This example uses nuxt default error page.
@ -260,7 +284,9 @@ axios: {
## Helpers ## Helpers
### Fetch Style requests ### Fetch Style requests
Axios plugin also supports fetch style requests with `$` prefixed methods: Axios plugin also supports fetch style requests with `$` prefixed methods:
```js ```js
// Normal usage with axios // Normal usage with axios
let data = (await $axios.get('...')).data let data = (await $axios.get('...')).data
@ -270,15 +296,17 @@ let data = await $axios.$get('...')
``` ```
### `setHeader(name, value, scopes='common')` ### `setHeader(name, value, scopes='common')`
Axios instance has a helper to easily set any header. Axios instance has a helper to easily set any header.
Parameters: Parameters:
- **name**: Name of the header
- **value**: Value of the header * **name**: Name of the header
- **scopes**: Send only on specific type of requests. Defaults * **value**: Value of the header
- Type: *Array* or *String* * **scopes**: Send only on specific type of requests. Defaults
- Defaults to `common` meaning all types of requests * Type: _Array_ or _String_
- Can be `get`, `post`, `delete`, ... * Defaults to `common` meaning all types of requests
* Can be `get`, `post`, `delete`, ...
```js ```js
// Adds header: `Authorization: 123` to all requests // Adds header: `Authorization: 123` to all requests
@ -288,22 +316,26 @@ this.$axios.setHeader('Authorization', '123')
this.$axios.setHeader('Authorization', '456') this.$axios.setHeader('Authorization', '456')
// Adds header: `Content-Type: application/x-www-form-urlencoded` to only post requests // Adds header: `Content-Type: application/x-www-form-urlencoded` to only post requests
this.$axios.setHeader('Content-Type', 'application/x-www-form-urlencoded', ['post']) this.$axios.setHeader('Content-Type', 'application/x-www-form-urlencoded', [
'post'
])
// Removes default Content-Type header from `post` scope // Removes default Content-Type header from `post` scope
this.$axios.setHeader('Content-Type', false, ['post']) this.$axios.setHeader('Content-Type', false, ['post'])
``` ```
### `setToken(token, type, scopes='common')` ### `setToken(token, type, scopes='common')`
Axios instance has an additional helper to easily set global authentication header. Axios instance has an additional helper to easily set global authentication header.
Parameters: Parameters:
- **token**: Authorization token
- **type**: Authorization token prefix(Usually `Bearer`). * **token**: Authorization token
- **scopes**: Send only on specific type of requests. Defaults * **type**: Authorization token prefix(Usually `Bearer`).
- Type: *Array* or *String* * **scopes**: Send only on specific type of requests. Defaults
- Defaults to `common` meaning all types of requests * Type: _Array_ or _String_
- Can be `get`, `post`, `delete`, ... * Defaults to `common` meaning all types of requests
* Can be `get`, `post`, `delete`, ...
```js ```js
// Adds header: `Authorization: 123` to all requests // Adds header: `Authorization: 123` to all requests
@ -323,6 +355,7 @@ this.$axios.setToken(false)
``` ```
## Dynamic API Backend ## Dynamic API Backend
Please notice that, `API_URL` is saved into bundle on build, CANNOT be changed Please notice that, `API_URL` is saved into bundle on build, CANNOT be changed
on runtime! You may use [proxy](../proxy) module for dynamically route api requests to different backend on test/staging/production. on runtime! You may use [proxy](../proxy) module for dynamically route api requests to different backend on test/staging/production.
@ -341,6 +374,7 @@ on runtime! You may use [proxy](../proxy) module for dynamically route api reque
``` ```
Start Nuxt Start Nuxt
``` ```
[AXIOS] Base URL: http://localhost:3000/api | Browser: /api [AXIOS] Base URL: http://localhost:3000/api | Browser: /api
[HPM] Proxy created: /api -> http://www.mocky.io [HPM] Proxy created: /api -> http://www.mocky.io
@ -348,6 +382,7 @@ Start Nuxt
``` ```
Now you can make requests to backend: (Works fine in both SSR and Browser) Now you can make requests to backend: (Works fine in both SSR and Browser)
```js ```js
async asyncData({ app }) { async asyncData({ app }) {
// Magically makes request to http://www.mocky.io/v2/59388bb4120000dc00a672e2 // Magically makes request to http://www.mocky.io/v2/59388bb4120000dc00a672e2
@ -360,13 +395,15 @@ async asyncData({ app }) {
``` ```
Details Details
- `'@nuxtjs/axios'`
- By default axios plugin sets base url to `http://[host]:[port]/api` which is `http://localhost:3000/api`
- `'/api': 'http://www.mocky.io/v2'` * `'@nuxtjs/axios'`
- This line creates a server middleware to pass requests from `/api` to `http://www.mocky.io/v2`
- We used `pathRewrite` to remove `/api` from starting of requests and change it to `/v2` * By default axios plugin sets base url to `http://[host]:[port]/api` which is `http://localhost:3000/api`
- For more information and advanced usage please refer to [proxy](https://github.com/nuxt-community/modules/blob/master/packages/proxy) docs.
* `'/api': 'http://www.mocky.io/v2'`
* This line creates a server middleware to pass requests from `/api` to `http://www.mocky.io/v2`
* We used `pathRewrite` to remove `/api` from starting of requests and change it to `/v2`
* For more information and advanced usage please refer to [proxy](https://github.com/nuxt-community/modules/blob/master/packages/proxy) docs.
## License ## License

View File

@ -1,31 +1,53 @@
const chalk = require('chalk') const chalk = require('chalk')
const path = require('path') const path = require('path')
const { URL } = require('whatwg-url')
const debug = require('debug')('nuxt:axios') const debug = require('debug')('nuxt:axios')
module.exports = function nuxtAxios (moduleOptions) { module.exports = function nuxtAxios (_moduleOptions) {
const port = process.env.PORT || process.env.npm_package_config_nuxt_port || 3000 // Combine options
let host = process.env.HOST || process.env.npm_package_config_nuxt_host || 'localhost' const moduleOptions = Object.assign({}, this.options.axios, _moduleOptions)
// Default port
const defaultPort =
process.env.API_PORT ||
moduleOptions.port ||
process.env.PORT ||
process.env.npm_package_config_nuxt_port ||
3000
// Default host
let defaultHost =
process.env.API_HOST ||
moduleOptions.host ||
process.env.HOST ||
process.env.npm_package_config_nuxt_host ||
'localhost'
/* istanbul ignore if */ /* istanbul ignore if */
if (host === '0.0.0.0') { if (defaultHost === '0.0.0.0') {
host = 'localhost' defaultHost = 'localhost'
} }
// Default prefix
const prefix = process.env.API_PREFIX || moduleOptions.prefix || '/'
// Apply defaults // Apply defaults
const defaults = { const options = Object.assign(
baseURL: `http://${host}:${port}/api`, {
browserBaseURL: null, baseURL: `http://${defaultHost}:${defaultPort}${prefix}`,
credentials: true, browserBaseURL: null,
proxyHeaders: true, proxyMode: false,
proxyHeadersIgnore: ['accept', 'host'], credentials: true,
debug: false, proxyHeaders: true,
disableDefaultErrorHandler: false, proxyHeadersIgnore: ['accept', 'host'],
redirectError: {} debug: false,
} disableDefaultErrorHandler: false,
redirectError: {}
},
moduleOptions
)
const options = Object.assign({}, defaults, this.options.axios, moduleOptions) // ENV overrides
// Override env
/* istanbul ignore if */ /* istanbul ignore if */
if (process.env.API_URL) { if (process.env.API_URL) {
options.baseURL = process.env.API_URL options.baseURL = process.env.API_URL
@ -36,12 +58,9 @@ module.exports = function nuxtAxios (moduleOptions) {
options.browserBaseURL = process.env.API_URL_BROWSER options.browserBaseURL = process.env.API_URL_BROWSER
} }
const isSchemeLessBaseURL = options.baseURL.substr(0, 2) === '//' // Default browserBaseURL
options.baseURL = new URL(options.baseURL, `http://${host}:${port}`)
if (!options.browserBaseURL) { if (!options.browserBaseURL) {
const sameHost = options.baseURL.host === `${host}:${port}` options.browserBaseURL = options.proxyMode ? prefix : options.baseURL
options.browserBaseURL = sameHost ? options.baseURL.pathname : isSchemeLessBaseURL ? options.baseURL.toString().substr(5) : options.baseURL // 5 == 'http:'.length
} }
// Register plugin // Register plugin
@ -52,7 +71,11 @@ module.exports = function nuxtAxios (moduleOptions) {
}) })
/* eslint-disable no-console */ /* eslint-disable no-console */
debug(`BaseURL: ${chalk.green(options.baseURL)} (Browser: ${chalk.green(options.browserBaseURL)})`) debug(
`BaseURL: ${chalk.green(options.baseURL)} (Browser: ${chalk.green(
options.browserBaseURL
)})`
)
} }
module.exports.meta = require('../package.json') module.exports.meta = require('../package.json')

View File

@ -25,8 +25,7 @@
"dependencies": { "dependencies": {
"axios": "^0.17.1", "axios": "^0.17.1",
"chalk": "^2.3.0", "chalk": "^2.3.0",
"debug": "^3.1.0", "debug": "^3.1.0"
"whatwg-url": "^6.4.0"
}, },
"devDependencies": { "devDependencies": {
"nuxt": "^1.1.1", "nuxt": "^1.1.1",

View File

@ -10,7 +10,8 @@ module.exports = {
modules: ['@@'], modules: ['@@'],
serverMiddleware: ['~/api.js'], serverMiddleware: ['~/api.js'],
axios: { axios: {
baseURL: `http://localhost:${process.env.PORT || 3000}/test_api`, prefix: `/test_api`,
proxyMode: true,
init (axios) {}, init (axios) {},
responseInterceptor: (response, { store }) => { responseInterceptor: (response, { store }) => {
/* eslint-disable no-console */ /* eslint-disable no-console */

View File

@ -7576,7 +7576,7 @@ whatwg-fetch@>=0.10.0:
version "2.0.3" version "2.0.3"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"
whatwg-url@^6.3.0, whatwg-url@^6.4.0: whatwg-url@^6.3.0:
version "6.4.0" version "6.4.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.4.0.tgz#08fdf2b9e872783a7a1f6216260a1d66cc722e08" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.4.0.tgz#08fdf2b9e872783a7a1f6216260a1d66cc722e08"
dependencies: dependencies: