Permalink
Browse files

feat(axios): New API

BREAKING CHANGE: API_PREFIX is deprecated.
  • Loading branch information...
pi0 committed Jun 9, 2017
1 parent 811b5a9 commit 01942269e95e18c1c48d8a82b7bca638369a3989
Showing with 176 additions and 93 deletions.
  1. +87 −14 modules/axios/README.md
  2. +25 −26 modules/axios/index.js
  3. +64 −53 modules/axios/plugin.js
@@ -1,12 +1,11 @@
# Axios
This plugin is a wrapper around [axios](https://github.com/mzabriskie/axios).
> Use [axios](https://github.com/mzabriskie/axios) with no pain!
- Sets default base URL.
- Handles all HTTP exceptions and prevents server side unhandled promise exceptions.
- Automatically set BaseURL for client & server side
- Injects `$get`,`$post`,... into vue context instances so requests can be done easily.
- Exposes `setToken` function to `$axios` so we can easily and globally set authentication tokens.
- Throws *nuxt-friendly* exceptions.
- Automatically enables `withCredentials` when requesting to default base URL.
- Automatically enables `withCredentials` when requesting to base URL.

## Setup
- Add `@nuxtjs/axios` dependency using yarn or npm to your project
@@ -19,30 +18,104 @@ This plugin is a wrapper around [axios](https://github.com/mzabriskie/axios).

## Usage

### Inside `asyncData`
### Component `asyncData`
```js
async asyncData({app: {$axios}}) {
async asyncData({app: { $axios }}) {
const {data} = await $axios.get('http://icanhazip.com')
return {
ip: data
}
}
```

### Inside component methods
### Component mixins
This mixins are available: `$request`, `$get`, `$delete`, `$head`, `$post`, `$put` & `$patch`

```js
async mounted() {
const {data} = await this.$get('http://icanhazip.com')
this.ip = data
}
```

## Customization
### Store `nuxtServerInit`
```js
async nuxtServerInit ({ commit }, { app: { $axios } }) {
const { data } = await $axios.get('http://icanhazip.com')
commit('SET_IP', data)
}
```

### Store actions
If you need axios instance in store actions, you may have to pass it when dispatching.

```js
// In components
export default {
methods: {
updateIP() {
this.$store.dispatch('getIP', { $axios: this.$axios })
}
}
}
// In store
{
actions: {
async getIP ({ commit }, { $axios }) {
const { data } = await $axios.get('http://icanhazip.com')
commit('SET_IP', data)
}
}
}
```

## Config
Config can be done using environment variables, `options.env` or module options.

Environment variable | Default | Description
---------------------|---------------------------------|--------------------------------------------
API_URL | http://[localhost]:[3000]/api | Base url for requests in server-side (SSR)
API_URL_BROWSER | /api (relative API_URL) | Base url for requests in client-side

## Dynamic API Backend
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.

**Example: (`nuxt.config.js`)**

```js
{
modules: [
'@nuxtjs/axios',
'@nuxtjs/proxy'
],
proxy: [
['/api', { target: 'http://www.mocky.io', pathRewrite: { '^/api': '/v2' } }]
]
}
```

Start Nuxt
```
[HPM] Proxy created: /api -> http://www.mocky.io
```

Customization can be done using shared environment variables.
Now you can make requests to backend: (Works fine in both SSR and Browser)
```
async asyncData({app}) {
// Magically makes request to http://www.mocky.io/v2/59388bb4120000dc00a672e2
const {data: {nuxt} } = await app.$axios.get('59388bb4120000dc00a672e2')
return {
nuxt // -> 'Works!'
}
}
```

Environment variable | Default | Description
---------------------|-------------------------|--------------------------------------------
API_URL | http://localhost:3000 | Base url for ajax requests in server-side
API_URL_BROWSER | [API_URL] | Base url for ajax requests in client-side
API_PREFIX | /api | Adds this prefix before all relative urls
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'`
- This line creates a server middleware to pass requests to `/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](../proxy) docs.
@@ -1,38 +1,37 @@
const chalk = require('chalk')
const path = require('path')
const { URL } = require('url')

module.exports = function nuxtAxios(options) {
// Register plugin
this.addPlugin({src: path.resolve(__dirname, 'plugin.js'), options})

// API URL
const API_URL = options.API_URL || process.env.API_URL || 'http://localhost:3000'
process.env.API_URL = API_URL
const fixUrl = url => url.replace(/\/\//g, '/').replace(':/', '://')

// API URL for Browser
const API_URL_BROWSER = options.API_URL_BROWSER || process.env.API_URL_BROWSER || API_URL
process.env.API_URL_BROWSER = API_URL_BROWSER
const port = process.env.PORT || process.env.npm_package_config_nuxt_port || 3000
const host = process.env.HOST || process.env.npm_package_config_nuxt_host || 'localhost'

// Common API Prefix
const API_PREFIX = options.API_PREFIX || process.env.API_PREFIX || '/api'
process.env.API_PREFIX = API_PREFIX

// Don't add env to production bundles
if (process.env.NODE_ENV !== 'production') {
this.options.env.API_URL = API_URL
this.options.env.API_URL_BROWSER = API_URL_BROWSER
module.exports = function nuxtAxios (options) {
// Get options
const getOpt = (key, default_val) => {
return process.env[key] || options[key] || options[key.toLowerCase()] || this.options.env[key] || default_val
}

printURL('API URL', API_URL, API_PREFIX)

if (API_URL_BROWSER !== API_URL) {
printURL('API URL (Browser)', API_URL_BROWSER, API_PREFIX)
const API_URL = getOpt('API_URL', `http://${host}:${port}/api`)
const API_URL_BROWSER = getOpt('API_URL_BROWSER', (new URL(API_URL)).pathname)

// Commit new values to all sources
const setOpt = (key, val) => {
process.env[key] = val
options[key] = val
this.options.env[key] = val
}
}
setOpt('API_URL', API_URL)
setOpt('API_URL_BROWSER', API_URL_BROWSER)

// Register plugin
this.addPlugin({
src: path.resolve(__dirname, 'plugin.js'),
options
})

function printURL(title, url, prefix) {
/* eslint-disable no-console */
console.log(chalk.bgMagenta.black(` ${title} `) + chalk.magenta(` ${url}${prefix}`) + '\r\n')
console.log(`[AXIOS] Base URL: ${chalk.green(API_URL)} | Browser: ${chalk.green(API_URL_BROWSER)}`)
}

module.exports.meta = require('./package.json')
@@ -1,91 +1,102 @@
import Axios from 'axios'
import Vue from 'vue'

const inBrowser = typeof window !== 'undefined'

const API_URL = inBrowser ? (process.env.API_URL_BROWSER || '') : (process.env.API_URL || 'http://localhost:3000')

// Try to get axios from components context, fallback to global Axios else
function getAxios() {
return (this && this.$root && this.$root.$options.$axios) ? this.$root.$options.$axios : Axios
// Make `this.$axios` available
if (!Vue.prototype.hasOwnProperty('$axios')) {
// Add mixin to add this._axios
Vue.mixin({
beforeCreate () {
// Check if `axios` has been defined in App
// Then fallback to $root.$axios
// Finally use global instance of Axios
this._axios = this.$options.axios || this.$root.$axios || Axios
}
})
// Add this.$axios instance
Object.defineProperty(Vue.prototype, '$axios', {
get () {
return this._axios
}
})
}

// Vue Component Mixins
Vue.mixin({
computed: {
$axios() {
return getAxios.call(this)
}
},
methods: {
// opts
$request(opts) {
return getAxios.call(this).request(opts);
$request (opts) {
return this.$axios.request(opts);
},
// url, opts
$get(url, opts) {
return getAxios.call(this).get(url, opts);
$get (url, opts) {
return this.$axios.get(url, opts);
},
$delete(url, opts) {
return getAxios.call(this).delete(url, opts);
$delete (url, opts) {
return this.$axios.delete(url, opts);
},
$head(url, opts) {
return getAxios.call(this).head(url, opts);
$head (url, opts) {
return this.$axios.head(url, opts);
},
// url, data, opts
$post(url, data, opts) {
return getAxios.call(this).post(url, data, opts);
$post (url, data, opts) {
return this.$axios.post(url, data, opts);
},
$put(url, data, opts) {
return getAxios.call(this).put(url, data, opts);
$put (url, data, opts) {
return this.$axios.put(url, data, opts);
},
$patch(url, data, opts) {
return getAxios.call(this).patch(url, data, opts);
$patch (url, data, opts) {
return this.$axios.patch(url, data, opts);
}
}
})

// Send credentials only to relative and API Backend requests
const withCredentials = config => {
if (config.withCredentials === undefined) {
if (!/^https?:\/\//i.test(config.url) || config.url.indexOf(process.env.API_URL_BROWSER) === 0 || config.url.indexOf(process.env.API_URL) === 0) {
config.withCredentials = true
}
}
return config
}

// Nuxt friendly http errors
const handleError = error => {
// const response = error.response || {}
// const config = error.config || {}
// TODO: Add integration with vue notification and nuxt.$error
// Avoid promise rejections in SSR context
return inBrowser? Promise.reject(error) : error
}

// Set requests token
function setToken(token, type = 'Bearer') {
function setToken (token, type = 'Bearer') {
if (!token) {
delete this.defaults.headers.common.Authorization;
return
}
this.defaults.headers.common.Authorization = (type ? type + ' ' : '') + token
}

export default ({app}) => {
export default (ctx) => {
const { app, store, redirect, req } = ctx

// Create new axios instance
const baseURL = process.env.browser
? (process.env.API_URL_BROWSER || '<%= options.API_URL_BROWSER %>')
: (process.env.API_URL || '<%= options.API_URL %>')

const axios = Axios.create({
baseURL: API_URL + (process.env.API_PREFIX || '/api')
baseURL
})

// Setup instance interceptors
axios.interceptors.request.use(withCredentials);
axios.interceptors.response.use(undefined, handleError);
// Send credentials only to relative and API Backend requests
axios.interceptors.request.use(config => {
if (config.withCredentials === undefined) {
if (!/^https?:\/\//i.test(config.url) || config.url.indexOf(baseURL) === 0) {
config.withCredentials = true
}
}
return config
});

// Error handler
axios.interceptors.response.use(undefined, (error) => {
if (error.response && error.response.status === 401) {
return redirect('/login')
}
console.log(error)
return Promise.reject(error)
});

// Make accessible using {this,$nuxt}.$root.$options.$axios
// Make accessible using app.$axios
app.$axios = axios

// setToken helper
// Plugin $axios into store instance
store.$axios = axios
ctx.$axios = axios

// token helper for authentication
axios.setToken = setToken.bind(axios)
}

0 comments on commit 0194226

Please sign in to comment.