管理多個JavaScript框架可能並不陌生,因此我們來看看如何一起使用Vue和Node來創建用戶管理系統
我們中的很多人已經從jQuery開始跳舞了多年的JavaScript框架洗牌,然後轉向Angular。但是Angular很複雜,所以我們轉向React。有了React,表面上看起來很簡單的事情最終會成為令人沮喪的混亂。輸入Vue.js. 它按預期工作。它很快。文件是令人難以置信的。模板是雄辯的。
關於如何處理狀態管理,有條件呈現,雙向綁定,路由等問題有一致的一致意見。我見過很多開發人員走這條路,所以今天我想帶你了解如何使用Vue.js和Node構建一個基本的應用程式。本教程將逐步介紹如何搭建Vue.js項目,將安全身份驗證卸載到Okta的OpenID Connect API(OIDC),鎖定受保護的路由以及通過後端REST API伺服器執行CRUD操作。本教程使用以下技術,但不需要深入了解相關知識:
帶有vue-cli, vue-router和 Okta Vue SDK的 Vue.js 使用 Express, Okta JWT驗證程序, Sequelize和 Epilogue結點
關於Vue.js
Vue.js是一個強大而簡單的JavaScript框架。它具有進入任何現代框架的最低障礙之一,同時為高性能Web應用程式提供所有必需的功能。
本教程涵蓋兩個主要構建,一個前端Web應用程式和後端REST API伺服器。前端將是一個帶有主頁,登錄和註銷的單頁面應用程式(SPA),以及一個職位經理。
Okta的OpenID Connect(OIDC) 將通過使用Okta的Vue SDK來處理我們的Web應用程式的身份驗證 。如果未經身份驗證的用戶導航到帖子管理器,則該Web應用程式應嘗試對用戶進行身份驗證。
伺服器將運行 Express 和 Sequelize 和 Epilogue。在高層次上,使用Sequelize和Epilogue,只需幾行代碼即可快速生成動態REST端點。
您將 在Express中間件的Web應用程式和Okta的JWT驗證程序發出請求時使用基於JWT的身份驗證 來驗證令牌。您的應用將公開以下端點,這些端點都需要具有有效訪問令牌的請求。
- GET /posts- GET /posts/:id- POST /posts- PUT /posts/:id- DELETE /posts/:id
創建您的Vue.js應用程式
為了讓您的項目快速起步,您可以利用vue-cli的腳手架功能 。在本教程中,您將使用 包含webpack, 熱重新加載,CSS提取和單元測試等少數功能 的 漸進式Web應用程式(PWA)模板。
如果您不熟悉PWA的原則,請查看我們的最新 漸進式Web應用程式指南。
要安裝 vue-cli 運行:
npm install -g vue-cli
接下來,您需要初始化您的項目。當你運行這個 vue init 命令時,只需要接受所有的默認值。
vue init pwa my-vue-appcd ./my-vue-appnpm installnpm run dev
http://localhost:8080指出你最喜歡的瀏覽器 ,你應該看到你的勞動成果:
額外獎勵:查看 可用 的其他 模板vue-cli。
安裝Bootstrap
讓我們安裝 bootstrap-vue, 以便利用各種預製 組件 (另外,您可以將注意力集中在功能上而不是定製CSS上):
npm i --save bootstrap-vue bootstrap
要完成安裝,請修改 ./src/main.js以包含 bootstrap-vue 並導入所需的CSS文件。你的 ./src/main.js文件應該是這樣的:
// The Vue build version to load with the `import` command// (runtime-only or standalone) has been set in webpack.base.conf with an alias.import Vue from 'vue'import App from './App'import router from './router'import BootstrapVue from 'bootstrap-vue'import 'bootstrap/dist/css/bootstrap.css'import 'bootstrap-vue/dist/bootstrap-vue.css'Vue.use(BootstrapVue)Vue.config.productionTip = false/* eslint-disable no-new */new Vue({el: '#app', router, template: '<App/>', components: { App }})
使用Okta添加身份驗證
處理Web應用程式中的身份驗證是每個開發人員存在的禍根。這就是Okta採用最少代碼保護您的Web應用程式的地方。要開始,您需要在Okta創建一個OIDC應用程式。 註冊一個永遠免費的開發者帳戶 (或者如果你已經有帳戶的話,請登錄)。
登錄後,點擊「添加應用程式」創建一個新的應用程式。
選擇「單頁面應用程式」平臺選項。
默認的應用程式設置應該與圖中所示的相同。
要安裝Okta Vue SDK,請運行以下命令:
npm i --save @okta/okta-vue
./src/router/index.js用下面的代碼打開 並替換整個文件。
import Vue from 'vue'import Router from 'vue-router'import Hello from '@/components/Hello'import PostsManager from '@/components/PostsManager'import Auth from '@okta/okta-vue'Vue.use(Auth, {issuer: 'https://{yourOktaDomain}.com/oauth2/default', client_id: '{yourClientId}', redirect_uri: 'http://localhost:8080/implicit/callback', scope: 'openid profile email'})Vue.use(Router)let router = new Router({ mode: 'history', routes: [ { path: '/', name: 'Hello', component: Hello }, { path: '/implicit/callback', component: Auth.handleCallback() }, { path: '/posts-manager', name: 'PostsManager', component: PostsManager, meta: { requiresAuth: true } } ]})router.beforeEach(Vue.prototype.$auth.authRedirectGuard())export default router
您需要替換 {yourOktaDomain} ,{yourClientId} 以及可以在Okta開發人員控制臺的應用程式概述頁面上找到的部分。這會將一個 authClient對象注入到您的Vue實例中,通過調用this.$auth Vue實例中的任何位置可以訪問它 。
Vue.use(Auth, {issuer: 'https://{yourOktaDomain}.com/oauth2/default', client_id: '{yourClientId}', redirect_uri: 'http://localhost:8080/implicit/callback', scope: 'openid profile email'})
Okta的身份驗證流程的最後一步是使用URL中的標記值將用戶重定向回您的應用程式。Auth.handleCallback()包含在SDK中的 組件處理重定向並在瀏覽器上保留令牌。
{path: '/implicit/callback', component: Auth.handleCallback()}
您還需要將受保護的路由鎖定為未經身份驗證的用戶訪問。這是通過實施導航警衛來完成的 。顧名思義, 導航衛士 主要用於通過重定向或取消來保護導航。
SDK附帶auth.authRedirectGuard() 檢查密鑰的匹配路由元數據的方法 , requiresAuth如果未通過身份驗證,則會將用戶重定向到身份驗證流程。
router.beforeEach(Vue.prototype.$auth.authRedirectGuard())
安裝此導航警衛後,具有以下元數據的任何路線都將受到保護。
meta: {requiresAuth: true}
在Vue中自定義您的應用布局
Web應用程式的布局位於組件中 ./src/App.vue。您可以使用 路由器視圖 組件為給定路徑呈現匹配的組件。
對於主菜單,您需要根據以下情況更改某些菜單項的可見性 activeUser:
未驗證:僅顯示 登錄已通過身份驗證:僅顯示 註銷
您可以使用v-if Vue.js中的指令來切換這些菜單項的可見性,該 指令檢查activeUser組件的存在 。當組件被加載(調用 created())或者當路由改變時,我們要刷新 activeUser。
打開 ./src/App.vue 並複製/粘貼以下代碼。
<template><div id="app"> <b-navbar toggleable="md" type="dark" variant="dark"> <b-navbar-toggle target="nav_collapse"></b-navbar-toggle> <b-navbar-brand to="/">My Vue App</b-navbar-brand> <b-collapse is-nav id="nav_collapse"> <b-navbar-nav> <b-nav-item to="/">Home</b-nav-item> <b-nav-item to="/posts-manager">Posts Manager</b-nav-item> <b-nav-item href="#" @click.prevent="login" v-if="!activeUser">Login</b-nav-item> <b-nav-item href="#" @click.prevent="logout" v-else>Logout</b-nav-item> </b-navbar-nav> </b-collapse> </b-navbar> <!-- routes will be rendered here --> <router-view /> </div></template><script>export default { name: 'app', data () { return { activeUser: null } }, async created () { await this.refreshActiveUser() }, watch: { // everytime a route is changed refresh the activeUser '$route': 'refreshActiveUser' }, methods: { login () { this.$auth.loginRedirect() }, async refreshActiveUser () { this.activeUser = await this.$auth.getUser() }, async logout () { await this.$auth.logout() await this.refreshActiveUser() this.$router.push('/') } }}</script>
每個登錄都必須註銷。以下片段將註銷您的用戶,刷新活動用戶(現在為空),然後將用戶重定向到主頁。當用戶點擊導航欄中的註銷連結時會調用此方法。
async logout () {await this.$auth.logout() await this.refreshActiveUser() this.$router.push('/')}
組件 是Vue.js中的構建塊。您的每個網頁都將在應用中定義為一個組件。由於vue-cli webpack模板使用 vue-loader,因此組件源文件有一個將模板,腳本和樣式分開的約定(請參見此處)。
現在您已添加vue-bootstrap,請修改 ./src/components/Hello.vue以移除vue-cli生成的樣板連結。
<template><div> <div> <h1>Hello World</h1> <p>This is the homepage of your vue app</p> </div> </div></template><style> .hero { height: 90vh; display: flex; align-items: center; justify-content: center; text-align: center; } .hero .lead { font-weight: 200; font-size: 1.5rem; }</style>
此時,您可以將Post Manager頁面存檔以測試您的身份驗證流程。確認身份驗證後,您將開始構建在您的帖子模型上執行CRUD操作所需的API調用和組件。
創建一個新文件 ./src/components/PostsManager.vue 並粘貼以下代碼:
<template><div> <h1>Posts Manager</h1> <p>Only authenticated users should see this page</p> </div></template>
以您的Vue.js前端和Auth流程進行試駕
在你的終端運行 npm run dev(如果它還沒有運行)。導航到 http://localhost:8080,你應該看到新的主頁。
如果你點擊發布 管理器或 登錄, 你應該被定向到Okta的流程。輸入您的Okta dev帳戶憑證。
注意:如果您登錄Okta開發者帳戶,您將被自動重定向回應用程式。您可以使用隱身或隱私瀏覽模式進行測試。
如果成功,您應該返回到登錄的首頁。
點擊 文章管理器連結應該呈現受保護的組件。
添加後端REST API伺服器
現在用戶可以安全地進行身份驗證,您可以構建REST API伺服器以在後期模型上執行CRUD操作。將以下依賴項添加到您的項目中:
npm i --save express cors @ okta / jwt-verifier sequelize sqlite3 epilogue axios
然後,創建該文件 ./src/server.js 並粘貼以下代碼。
const express = require('express')const cors = require('cors')const bodyParser = require('body-parser')const Sequelize = require('sequelize')const epilogue = require('epilogue')const OktaJwtVerifier = require('@okta/jwt-verifier')const oktaJwtVerifier = new OktaJwtVerifier({clientId: '{yourClientId}', issuer: 'https://{yourOktaDomain}.com/oauth2/default'})let app = express()app.use(cors())app.use(bodyParser.json())// verify JWT token middlewareapp.use((req, res, next) => { // require every request to have an authorization header if (!req.headers.authorization) { return next(new Error('Authorization header is required')) } let parts = req.headers.authorization.trim().split(' ') let accessToken = parts.pop() oktaJwtVerifier.verifyAccessToken(accessToken) .then(jwt => { req.user = { uid: jwt.claims.uid, email: jwt.claims.sub } next() }) .catch(next) // jwt did not verify!})// For ease of this tutorial, we are going to use SQLite to limit dependencieslet database = new Sequelize({ dialect: 'sqlite', storage: './test.sqlite'})// Define our Post model// id, createdAt, and updatedAt are added by sequelize automaticallylet Post = database.define('posts', { title: Sequelize.STRING, body: Sequelize.TEXT})// Initialize epilogueepilogue.initialize({ app: app, sequelize: database})// Create the dynamic REST resource for our Post modellet userResource = epilogue.resource({ model: Post, endpoints: ['/posts', '/posts/:id']})// Resets the database and launches the express app on :8081database .sync({ force: true }) .then(() => { app.listen(8081, () => { console.log('listening to port localhost:8081') }) })
請務必將這些變量 {yourOktaDomain} 並 {clientId}從1563您OIDC應用在上面的代碼值。
添加續集
Sequelize 是Node.js基於承諾的ORM。它支持方言PostgreSQL,MySQL,SQLite和MSSQL,並具有堅實的事務支持,關係,讀取複製等功能。
為了簡化本教程,您將使用SQLite來限制外部依賴關係。以下代碼使用SQLite作為驅動程序初始化Sequelize實例。
let database = new Sequelize({dialect: 'sqlite', storage: './test.sqlite'})
每個帖子都有一個 title 和 body。(欄位 createdAt,和 updatedAt 由Sequelize自動添加)。通過Sequelize,您可以通過調用define() 實例來定義模型 。
letPost=database.define('posts', {title: Sequelize.STRING,body: Sequelize.TEXT})
添加尾聲
Epilogue 通過Express應用程式中的Sequelize模型創建靈活的REST端點。如果你編碼REST端點,你知道有多少重複。DRY FTW!
// Initialize epilogueepilogue.initialize({app: app, sequelize: database})// Create the dynamic REST resource for our Post modellet userResource = epilogue.resource({ model: Post, endpoints: ['/posts', '/posts/:id']})
驗證您的JWT
這是REST API伺服器中最重要的組件。沒有這個中間件,任何用戶都可以在我們的資料庫上執行CRUD操作。如果沒有授權頭存在,或者訪問令牌無效,則API調用將失敗並返回錯誤。
// verify JWT token middlewareapp.use((req, res, next) => {// require every request to have an authorization header if (!req.headers.authorization) { return next(new Error('Authorization header is required')) } let parts = req.headers.authorization.trim().split(' ') let accessToken = parts.pop() oktaJwtVerifier.verifyAccessToken(accessToken) .then(jwt => { req.user = { uid: jwt.claims.uid, email: jwt.claims.sub } next() }) .catch(next) // jwt did not verify!})
運行伺服器
打開一個新的終端窗口並使用該命令運行伺服器 node ./src/server。您應該看到來自Sequelize的調試信息以及在埠8081上偵聽的應用程式。
完成郵件管理器組件
現在REST API伺服器已經完成,您可以開始連線您的發布管理器來獲取帖子,創建帖子,編輯帖子和刪除帖子。
我總是將我的API集成集中到一個輔助模塊中。這使得組件中的代碼更加清潔,並提供單個位置,以防您需要使用API請求更改任何內容。
創建一個文件 ./src/api.js 並將以下代碼複製/粘貼到其中:
import Vue from 'vue'import axios from 'axios'const client = axios.create({baseURL: 'http://localhost:8081/', json: true})export default { async execute (method, resource, data) { // inject the accessToken for each request let accessToken = await Vue.prototype.$auth.getAccessToken() return client({ method, url: resource, data, headers: { Authorization: `Bearer ${accessToken}` } }).then(req => { return req.data }) }, getPosts () { return this.execute('get', '/posts') }, getPost (id) { return this.execute('get', `/posts/${id}`) }, createPost (data) { return this.execute('post', '/posts', data) }, updatePost (id, data) { return this.execute('put', `/posts/${id}`, data) }, deletePost (id) { return this.execute('delete', `/posts/${id}`) }}
當您使用OIDC進行身份驗證時,訪問令牌將在瀏覽器中本地保存。由於每個API請求都必須具有訪問令牌,因此您可以從認證客戶端獲取並將其設置在請求中。
let accessToken = await Vue.prototype.$auth.getAccessToken()return client({method, url: resource, data, headers: { Authorization: `Bearer ${accessToken}` }})
通過在您的API幫助器中創建以下代理方法,輔助模塊外部的代碼仍然保持清晰和語義。
getPosts () {return this.execute('get', '/posts')},getPost (id) { return this.execute('get', `/posts/${id}`)},createPost (data) { return this.execute('post', '/posts', data)},updatePost (id, data) { return this.execute('put', `/posts/${id}`, data)},deletePost (id) { return this.execute('delete', `/posts/${id}`)}
您現在擁有了連接您的發布者管理器組件以通過REST API進行CRUD操作所需的所有組件。打開 ./src/components/PostsManager.vue 並複製/粘貼以下代碼。
<template><div> <h1>Posts Manager</h1> <b-alert :show="loading" variant="info">Loading...</b-alert> <b-row> <b-col> <table> <thead> <tr> <th>ID</th> <th>Title</th> <th>Updated At</th> <th> </th> </tr> </thead> <tbody> <tr v-for="post in posts" :key="post.id"> <td>{{ post.id }}</td> <td>{{ post.title }}</td> <td>{{ post.updatedAt }}</td> <td> <a href="#" @click.prevent="populatePostToEdit(post)">Edit</a> - <a href="#" @click.prevent="deletePost(post.id)">Delete</a> </td> </tr> </tbody> </table> </b-col> <b-col lg="3"> <b-card :title="(model.id ? 'Edit Post ID#' + model.id : 'New Post')"> <form @submit.prevent="savePost"> <b-form-group label="Title"> <b-form-input type="text" v-model="model.title"></b-form-input> </b-form-group> <b-form-group label="Body"> <b-form-textarea rows="4" v-model="model.body"></b-form-textarea> </b-form-group> <div> <b-btn type="submit" variant="success">Save Post</b-btn> </div> </form> </b-card> </b-col> </b-row> </div></template><script>import api from '@/api'export default { data () { return { loading: false, posts: [], model: {} } }, async created () { this.refreshPosts() }, methods: { async refreshPosts () { this.loading = true this.posts = await api.getPosts() this.loading = false }, async populatePostToEdit (post) { this.model = Object.assign({}, post) }, async savePost () { if (this.model.id) { await api.updatePost(this.model.id, this.model) } else { await api.createPost(this.model) } this.model = {} // reset form await this.refreshPosts() }, async deletePost (id) { if (confirm('Are you sure you want to delete this post?')) { // if we are editing a post we deleted, remove it from the form if (this.model.id === id) { this.model = {} } await api.deletePost(id) await this.refreshPosts() } } }}</script>
清單的帖子
您將使用 api.getPosts() 從REST API伺服器獲取帖子。您應該在組件加載時以及任何變更操作(創建,更新或刪除)後刷新帖子列表。
async refreshPosts () {this.loading = true this.posts = await api.getPosts() this.loading = false}
該屬性 this.loading 被切換,以便UI可以反映未決的API調用。您可能看不到加載消息,因為API請求沒有發送到網際網路。
創建帖子
組件中包含表單以保存帖子。savePosts() 當表單被提交並且其輸入被綁定到model組件上的對象時, 它被連接起來調用 。
何時 savePost() 被調用,它將執行更新或基於存在的創建 model.id。這通常是不必為創建和更新定義兩個單獨表單的捷徑。
async savePost () {if (this.model.id) { await api.updatePost(this.model.id, this.model) } else { await api.createPost(this.model) } this.model = {} // reset form await this.refreshPosts()}
更新帖子
更新帖子時,您首先必須將帖子加載到表單中。這設置 model.id 觸發器更新的內容 savePost()。
async populatePostToEdit (post) {this.model = Object.assign({}, post)}
重要提示:在 Object.assign() 通話複製後的說法,而不是參考價值。在處理Vue中的對象變異時,應該始終設置為值,而不是引用。
刪除帖子
要刪除一個帖子只需調用 api.deletePost(id)。刪除前確認總是很好,所以讓我們在本地確認提醒框中確認點擊是有意的。
async deletePost (id) {if (confirm('Are you sure you want to delete this post?')) { await api.deletePost(id) await this.refreshPosts() }}
測試你的Vue.js + Node CRUD應用程式
確保伺服器和前端都在運行。
Terminal #1
node ./src/server
Terminal #2
npm run dev
導航 http://localhost:8080 並給它一個旋風。
用Vue做更多!
正如我在這篇文章的頂部所說的,我認為Vue高於其他框架。這裡有五個原因:
簡單的組件生命周期基於HTML的模板 和本地 雙向綁定廣泛同意處理 路由, 狀態管理, webpack配置和 同構web應用的方式龐大的社區支持 資源,組件,庫和項目Vue感覺與React (沒有JSX!)非常相似, 這降低了React經驗的入門門檻。在React和Vue之間移動並不是很困難。
我在本教程中涵蓋了大量材料,但如果您第一次沒有掌握所有內容,請不要感到難過。使用這些技術越多,他們就會越熟悉。