先日2019/08/26(月)に、NuxtMeetup#9で、
Nuxt.js+TypescriptでのserverMiddleware、のお話をさせていただきました。
その時のスライドはこちらですー
Nuxt.jsのベストプラクティスを考えてみる
もう少し、実践的な補足を記載しようかと思います。
私がよく使う自分でまとめたテンプレ
からちょっとソース抜粋してご説明します。テンプレはちょっと更新してないので、version 2.6.3ですが、この頃から serverMiddlewareの機能は変わってないので、2.9.xにあげるのを迷ってる方も同じようにserverMiddleware使えます。
APIの定義方法パターン
Nuxt.jsの公式では、
// nuxt.config.ts
serverMiddleware: [
{ path: '/api', handler: '~/api/index.ts' },
]
ですが、私のスライドではこうなってたかと思います。
// nuxt.config.ts
serverMiddleware: ['~/server'],
nuxt.config.tsに一切APIのパスを書かず、全てexpress側で書くことができます。
たとえば
// Nuxtからは叩かず、制御用のAPI
/common/health // ロードバランサー用のヘルスチェックAPI
/common/auth // 認証用共通API
// アプリケーション用のNuxtから叩くAPI
/api/hoge1
/api/hoge2
こんな感じで、パスを分けた場合でもindex.tsのみの修正でOKです。
const router = Router()
router.get('/health', (req: Request, res: Response | any, next: NextFunction) => {
try {
res.sendStatus(200)
} catch (e) {
next(e)
}
})
router.get('/auth', (req: Request, res: Response | any, next: NextFunction) => {
try {
// 必要な処理
res.sendStatus(200)
} catch (e) {
next(e)
}
})
export default router
// commonApi.ts
const router = Router()
router.get('/hoge1', (req: Request, res: Response | any, next: NextFunction) => {
try {
// 必要な処理
res.sendStatus(200)
} catch (e) {
next(e)
}
})
router.get('/hoge2', (req: Request, res: Response | any, next: NextFunction) => {
try {
// 必要な処理
res.sendStatus(200)
} catch (e) {
next(e)
}
})
export default router
// src/server/index/ts
import basicAuth from 'basic-auth-connect'
import bodyParser from 'body-parser'
import cookieParser from 'cookie-parser'
import express from 'express'
import dotenv from 'dotenv'
import helmet from 'helmet'
import session from './middleware/session'
import commonApi from './commonApi'
import appApi from './appApi'
dotenv.config()
const app = express()
app.use(bodyParser.json())
app.use(cookieParser())
app.use(session())
app.use(helmet())
app.use(helmet.noCache())
// こういった感じでexpressでパスのルールを固定できます。
// これはexpress純粋の機能なので、nuxtには依存しません
app.use('/common', commonApi)
app.use('/api', appApi)
module.exports = app
派生パターンとしては、こういう風にパスの階層を元では持たせず、それぞれに任せることもできます。
// api.ts
// commonApi.ts
const router = Router()
router.get('/api/hoge1', (req: Request, res: Response | any, next: NextFunction) => {
try {
// 必要な処理
res.sendStatus(200)
} catch (e) {
next(e)
}
})
export default router
// src/server/index/ts
import appApi from './appApi'
// 省略
app.use(helmet.noCache())
// パスの階層をここでは持たせず、それぞれに任せることもできます
app.use(appApi)
他にもパターンはいくつかありますが、expressの機能になりますので、より詳しく知りたい方はexpressのドキュメントをみると良いかと思います。
NuxtからAPIを実行する場合
Nuxtが動いているサーバーと同じところにAPIを作るので、Nuxtのページからaxiosを使ってリクエストする先が、自分自身になります。
Nuxt使ってる方はasyncData をよく使うと思いますが、本番運用する場合 nuxt.config.tsに設定を追加が必要になります。
実際に設定する内容
// nuxt.config.ts
/*
** Axios module configuration
*/
axios: {
baseURL: 'http://localhost:3000',
browserBaseURL: 'http://xxxxxx.com' // 自分のサイトのドメイン
},
なぜこうするかですが、nuxtのaxiosをみると、
baseURLが http://localhost:3000/
になってしまいます。
URLの役割ですが、
- SSR時は、baseURLにアクセスする
- SPA時は、browserBaseURLにアクセスする
です。
asyncDataは、SSRとSPAで動きますが、ローカルでの開発の場合、SSR時もSPA時でもhttp://localhost:3000/
なので自分自身を参照してるためアクセスできます。
productionやstaging環境などサーバーにdeployして利用する際は、何も指定していないと、SPAの時も、http://localhost:3000/api/hoge1
のようになってしまいアクセスできません。なので、ブラウザの時に自分自身にアクセスできるようにbaseURL、browserBaseURLの指定が必要になります。
また、CloudFront等のCDNを利用している場合は、baseURLをlocalhostにしてしまうとCDNを通らずAPIにキャッシュが効かないので、browserBaseURLと同じdomainをあえてbaseURLにいれて、CDN経由でAPIにアクセスするということもできます。
// nuxt.config.ts
axios: {
baseURL: 'https://xxxxxx.com', // 両方とも同じにして、SSR時もCDN経由させる
browserBaseURL: 'https://xxxxxx.com'
},
ご参考に
私のリポジトリの
https://github.com/tanaka-yui/nuxtjs-base-template
には、私が自作したAPIのvalidationをするミドルウェアや、Redisを使ったsession共有のミドルウェアなど(このへん)
実際にそのまま使えるものも入っております。
こういう風に自前で作ったmiddlewareをAPIで利用できます
// JavaのSpringっぽく、バリデーション条件をchainするようになってます。
router.get(
'/api/list'/,
[
Authenticated,
Validator([
new ValidatorItem('date').date(),
new ValidatorItem('type').number().required(),
])
],
async (req: Request, res: Response | any, next: NextFunction) => {
try {
res.response(await services.getList(req))
} catch (e) {
next(e)
}
}
)
expressなどのmiddlewareを自由に作れる様になると色々と便利になると思うのでご参考にしていただけるとー