File Exists: /node_modules/.bin/node-pre-gyp Move It Away
I've danced the JavaScript framework shuffle for years starting with jQuery, then on to Angular. After being frustrated with Angular's complexity, I found React and thought I was in the clear. What seemed simple on the surface concluded upward beingness a frustrating mess. And then I institute Vue.js. It just felt correct. Information technology worked as expected. Information technology was fast. The documentation was incredible. Templating was eloquent. There was a unanimous consensus around how to handle state management, conditional rendering, two-way binding, routing, and more.
This tutorial will accept you stride by stride through scaffolding a Vue.js projection, offloading secure authentication to Okta'due south OpenID Connect API (OIDC), locking down protected routes, and performing Crud operations through a backend Residuum API server. This tutorial uses the post-obit technologies but doesn't require intimate cognition to follow along:
- Vue.js with Vue CLI, vue-router, and the Okta Vue SDK
- Node with Express, Okta JWT Verifier, Sequelize, and Finale
Tabular array of Contents
- About Vue.js
- Create Your Vue.js App
- Install Bootstrap
- Add together Authentication with Okta
- Customize Your App Layout in Vue
- Take Your Vue.js Frontend and Auth Flows for a Test Drive
- Add a Backend REST API Server
- Add together Sequelize
- Add Finale
- Verify Your JWT
- Run the Server
- Complete the Posts Manager Component
- Listing Posts
- Creating Posts
- Updating Posts
- Deleting Posts
- Test Your Vue.js + Node CRUD App
- Exercise More With Vue!
Virtually Vue.js
Vue.js is a robust simply simple JavaScript framework. It has one of the lowest barriers to entry of whatever modern framework while providing all the required features for high performance web applications.
This tutorial covers 2 primary builds, a frontend web app and backend REST API server. The frontend will be a unmarried page application (SPA) with a homepage, login and logout, and a posts manager.
Okta'due south OpenID Connect (OIDC) will handle our spider web app's hallmark through the use of Okta's Vue SDK. If an unauthenticated user navigates to the posts manager, the web app should endeavor to authenticate the user.
The server will run Limited with Sequelize and Finale. At a high level, with Sequelize and Finale you can apace generate dynamic Residue endpoints with only a few lines of code.
You will use JWT-based authentication when making requests from the spider web app and Okta'due south JWT Verifier in an Express middleware to validate the token. Your app volition betrayal the following endpoints which all require requests to have a valid access token.
- GET /posts - GET /posts/:id - POST /posts - PUT /posts/:id - DELETE /posts/:id
Create Your Vue.js App
To get your project off the basis speedily you tin can leverage the scaffolding functionality from Vue CLI. For this tutorial, you are going to apply the progressive web app (PWA) template that includes a handful of features including webpack, hot reloading, CSS extraction, and unit testing.
If you're not familiar with the tenets of PWA, check out our ultimate guide to progressive web applications.
To install the Vue CLI run:
npm install -g @vue/cli@4.5.12 @vue/cli-init@4.five.12
Adjacent, you need to initialize your projection. When you run the vue init
control just take all the default values.
vue init pwa my-vue-app cd ./my-vue-app npm install npm run dev
Signal your favorite browser to http://localhost:8080
and you should see the fruits of your labor:
Extra Credit: Check out the other templates available for the Vue CLI.
Install Bootstrap
Let'due south install bootstrap-vue and so y'all tin can accept advantage of the diverse premade components (plus you can keep the focus on functionality and not on custom CSS):
npm i bootstrap-vue@2.21.ii bootstrap@4.five.3
To complete the installation, change ./src/primary.js
to include bootstrap-vue and import the required CSS files. Your ./src/main.js
file should look like this:
// The Vue build version to load with the `import` command // (runtime-but or standalone) has been set up 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 } })
Add Hallmark with Okta
Dealing with hallmark in a web app is the bane of every developer's existence. That's where Okta comes in to secure your web applications with minimal lawmaking.
Before yous begin, you'll demand a free Okta developer account. Install the Okta CLI and run okta register
to sign up for a new account. If you already accept an account, run okta login
. Then, run okta apps create
. Select the default app proper noun, or alter it every bit you run across fit. Choose Unmarried-Page App and press Enter.
Employ http://localhost:8080/callback
for the Redirect URI and have the default Logout Redirect URI of http://localhost:8080
.
What does the Okta CLI practise?
The Okta CLI will create an OIDC Single-Folio App in your Okta Org. It volition add the redirect URIs you specified and grant access to the Everyone group. It will also add together a trusted origin for http://localhost:8080
. You will see output like the post-obit when information technology's finished:
Okta awarding configuration: Issuer: https://dev-133337.okta.com/oauth2/default Client ID: 0oab8eb55Kb9jdMIr5d6
NOTE: You can likewise apply the Okta Admin Console to create your app. Run into Create a Vue App for more information.
So, install the Okta Vue SDK and its peer dependency, the Okta AuthJS SDK:
npm i @okta/okta-vue@iii.1.0 @okta/okta-auth-js@4.eight.0
Open ./src/router/alphabetize.js
and supervene upon the unabridged file with the post-obit code.
import Vue from ' vue ' import Router from ' vue-router ' import Hi from ' @/components/Hello ' import PostsManager from ' @/components/PostsManager ' import OktaVue , { LoginCallback } from ' @okta/okta-vue ' import { OktaAuth } from ' @okta/okta-auth-js ' const oktaAuth = new OktaAuth ({ issuer : ' https://{yourOktaDomain} /oauth2/default ' , clientId : ' {clientId} ' , redirectUri : window . location . origin + ' /callback ' , scopes : [ ' openid ' , ' contour ' , ' e-mail ' ] }) Vue . apply ( Router ) Vue . use ( OktaVue , { oktaAuth }) let router = new Router ({ mode : ' history ' , routes : [ { path : ' / ' , name : ' Hello ' , component : How-do-you-do }, { path : ' /callback ' , component : LoginCallback }, { path : ' /posts-managing director ' , name : ' PostsManager ' , component : PostsManager , meta : { requiresAuth : true } } ] }) consign default router
Yous'll need to replace {yourOktaDomain}
and {clientId}
with the values from the app you lot simply created. This volition inject an authClient
object into your Vue instance which tin can exist accessed past calling this.$auth
anywhere inside your Vue instance.
The final step of Okta's hallmark menses is redirecting the user dorsum to your app with the token values in the URL. The LoginCallback
component included in the SDK handles the redirect and persists the tokens on the browser.
{ path : ' /callback ' , component : LoginCallback }
You besides need to lock downwardly protected routes from being accessed by unauthenticated users. This is accomplished by implementing a navigation baby-sit. As the name suggests, navigation guards are primarily used to guard navigations either by redirecting or canceling.
The Okta Vue SDK comes with navigation guards congenital-in, so any road that has the following metadata will exist protected.
meta : { requiresAuth : true }
Customize Your App Layout in Vue
The web app's layout is located in a component ./src/App.vue
. You lot can use the router-view component to render the matched component for the given path.
For the main menu, yous'll desire to alter the visibility of certain menu items based on the condition of the activeUser
:
- Not Authenticated: Evidence only Login
- Authenticated: Testify only Logout
You can toggle the visibility of these menu items using the v-if
directive in Vue.js that checks the existence of activeUser
on the component. When the component is loaded (which calls created()
) or when a route changes we want to refresh the activeUser
.
Open ./src/App.vue
and re-create/paste the post-obit code.
<template> <div id= "app" > <b-navbar toggleable= "md" blazon= "dark" variant= "nighttime" > <b-navbar-toggle target= "nav_collapse" ></b-navbar-toggle> <b-navbar-brand to= "/" >My Vue App</b-navbar-make> <b-collapse is-nav id= "nav_collapse" > <b-navbar-nav> <b-nav-item to= "/" >Dwelling house</b-nav-particular> <b-nav-item to= "/posts-director" >Posts Manager</b-nav-item> <b-nav-item href= "#" @ click.preclude= "login" five-if= "!activeUser" >Login</b-nav-item> <b-nav-item href= "#" @ click.preclude= "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 { proper name : ' app ' , data () { render { activeUser : null } }, async created () { wait this . refreshActiveUser () }, picket : { // everytime a route is changed refresh the activeUser ' $road ' : ' refreshActiveUser ' }, methods : { async login () { this . $auth . signInWithRedirect () }, async refreshActiveUser () { if ( this . authState . isAuthenticated ) { this . activeUser = wait this . $auth . getUser () } }, async logout () { await this . $auth . signOut () expect this . refreshActiveUser () this . $router . push ( ' / ' ) } } } </script>
Every login must have a logout. The post-obit snippet will log out your user and and then redirect the user to the homepage. This method is chosen when a user clicks on the logout link in the nav.
async logout () { await this . $auth . signOut () }
Components are the building blocks within Vue.js. Each of your pages will be defined in the app as a component. Since the Vue CLI webpack template utilizes vue-loader, your component source files have a convention that separates template, script, and style (run into here).
Now that you've added vue-bootstrap, change ./src/components/Hello.vue
to remove the average links vue-cli generates.
<template> <div course= "hero" > <div> <h1 class= "display-iii" >Hello Earth</h1> <p class= "lead" >This is the homepage of your vue app</p> </div> </div> </template> <style> .hero { summit : 90vh ; display : flex ; align-items : center ; justify-content : centre ; text-marshal : center ; } .hero .atomic number 82 { font-weight : 200 ; font-size : one.5rem ; } </style>
At this point you lot can stub out the Post Manager page to examination your authentication menstruation. Once y'all ostend authentication works, you'll start to build out the API calls and components required to perform CRUD operations on your Posts model.
Create a new file ./src/components/PostsManager.vue
and paste the post-obit lawmaking:
<template> <div class= "container-fluid mt-4" > <h1 class= "h1" >Posts Manager</h1> <p>Simply authenticated users should meet this page</p> </div> </template>
Take Your Vue.js Frontend and Auth Flows for a Test Drive
In your last run npm run dev
(if information technology's not already running). Navigate to http://localhost:8080
and y'all should see the new homepage.
If you click Posts Manager or Login you should be directed to Okta'southward flow. Enter your Okta developer account credentials.
Note: If you are logged in to your Okta Developer Account you will be redirected automatically dorsum to the app. You can exam this by using incognito or private browsing mode.
If successful, you should render to the homepage logged in.
Clicking on Posts Manager link should render the protected component.
Add a Backend Balance API Server
Now that users tin securely authenticate, you can build the Residual API server to perform CRUD operations on a post model. Add the following dependencies to your projection:
npm i limited@4.17.1 cors@2.viii.5 @okta/jwt-verifier@2.1.0 \ sequelize@6.vi.2 sqlite3@5.0.2 finale-residual@1.1.1 axios@0.21.1
Then, create the file ./src/server.js
and paste the following code.
const express = require ( ' express ' ) const cors = require ( ' cors ' ) const bodyParser = require ( ' trunk-parser ' ) const Sequelize = require ( ' sequelize ' ) const finale = crave ( ' finale-rest ' ) const OktaJwtVerifier = require ( ' @okta/jwt-verifier ' ) const oktaJwtVerifier = new OktaJwtVerifier ({ clientId : ' {clientId} ' , issuer : ' https://{yourOktaDomain} /oauth2/default ' }) let app = express () app . utilise ( cors ()) app . employ ( bodyParser . json ()) // verify JWT token middleware app . apply (( req , res , next ) => { // require every request to have an dominance header if ( ! req . headers . authorisation ) { return side by side ( new Error ( ' Authorization header is required ' )) } let parts = req . headers . authorization . trim (). dissever ( ' ' ) permit accessToken = parts . pop () oktaJwtVerifier . verifyAccessToken ( accessToken , ' api://default ' ) . then ( jwt => { req . user = { uid : jwt . claims . uid , email : jwt . claims . sub } next () }) . catch ( next ) // jwt did non verify! }) // For ease of this tutorial, we are going to utilize SQLite to limit dependencies let database = new Sequelize ({ dialect : ' sqlite ' , storage : ' ./exam.sqlite ' }) // Define our Postal service model // id, createdAt, and updatedAt are added by sequelize automatically let Post = database . define ( ' posts ' , { title : Sequelize . Cord , body : Sequelize . TEXT }) // Initialize finale finale . initialize ({ app : app , sequelize : database }) // Create the dynamic REST resource for our Post model allow userResource = finale . resource ({ model : Post , endpoints : [ ' /posts ' , ' /posts/:id ' ] }) // Resets the database and launches the express app on :8081 database . sync ({ force : true }) . and so (() => { app . heed ( 8081 , () => { console . log ( ' listening to port localhost:8081 ' ) }) })
Brand sure to replace the variables {yourOktaDomain}
and {clientId}
in the above code with values from your OIDC app in Okta.
Add together Sequelize
Sequelize is a promise-based ORM for Node.js. It supports the dialects PostgreSQL, MySQL, SQLite, and MSSQL and features solid transaction back up, relations, read replication, and more.
For ease of this tutorial, you're going to employ SQLite to limit external dependencies. The following code initializes a Sequelize example using SQLite as your driver.
allow database = new Sequelize ({ dialect : ' sqlite ' , storage : ' ./exam.sqlite ' })
Each post has a championship
and torso
. (The fields createdAt
, and updatedAt
are added past Sequelize automatically). With Sequelize, you define models by calling define()
on your case.
let Post = database . define ( ' posts ' , { championship : Sequelize . String , body : Sequelize . TEXT })
Add together Finale
Finale creates flexible Rest endpoints from Sequelize models within an Express app. If you ever coded Rest endpoints you know how much repetition at that place is. D.R.Y. FTW!
// Initialize finale finale . initialize ({ app : app , sequelize : database }) // Create the dynamic Balance resource for our Postal service model let userResource = finale . resource ({ model : Post , endpoints : [ ' /posts ' , ' /posts/:id ' ] })
Verify Your JWT
This is the most crucial component of your REST API server. Without this middleware whatsoever user can perform Crud operations on our database. If no potency header is present, or the access token is invalid, or the audience doesn't match, the API telephone call will fail and render an mistake.
// verify JWT token middleware app . use (( req , res , adjacent ) => { // require every request to have an say-so header if ( ! req . headers . dominance ) { return next ( new Mistake ( ' Authorization header is required ' )) } let parts = req . headers . authorization . trim (). split ( ' ' ) let accessToken = parts . pop () oktaJwtVerifier . verifyAccessToken ( accessToken , ' api://default ' ) . then ( jwt => { req . user = { uid : jwt . claims . uid , email : jwt . claims . sub } next () }) . grab ( next ) // jwt did not verify! })
Run the Server
Open a new terminal window and run the server with the control node ./src/server
. You lot should see debug information from Sequelize and the app listening on port 8081.
Complete the Posts Director Component
Now that the Residue API server is complete, you can start wiring upwards your posts manager to fetch posts, create posts, edit posts, and delete posts.
I always centralize my API integrations into a single helper module. This keeps the code in components much cleaner and provides single location in case you lot need to change anything with the API asking.
Create a file ./src/api.js
and copy/paste the following lawmaking into it:
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 = look Vue . image . $auth . getAccessToken () return client ({ method , url : resources , information , headers : { Say-so : `Bearer ${ accessToken } ` } }). and then ( req => { return req . data }) }, getPosts () { return this . execute ( ' get ' , ' /posts ' ) }, getPost ( id ) { render this . execute ( ' get ' , `/posts/ ${ id } ` ) }, createPost ( data ) { render this . execute ( ' post ' , ' /posts ' , data ) }, updatePost ( id , data ) { return this . execute ( ' put ' , `/posts/ ${ id } ` , information ) }, deletePost ( id ) { return this . execute ( ' delete ' , `/posts/ ${ id } ` ) } }
When yous cosign with OIDC, an access token is persisted locally in the browser. Since each API asking must have an access token, you tin fetch it from the authentication customer and set up information technology in the asking.
let accessToken = look Vue . prototype . $auth . getAccessToken () return customer ({ method , url : resource , data , headers : { Authorization : `Bearer ${ accessToken } ` } })
By creating the following proxy methods within your API helper, the code outside the helper module remains make clean and semantic.
getPosts () { render this . execute ( ' get ' , ' /posts ' ) }, getPost ( id ) { return this . execute ( ' become ' , `/posts/ ${ id } ` ) }, createPost ( data ) { render this . execute ( ' post ' , ' /posts ' , data ) }, updatePost ( id , information ) { return this . execute ( ' put ' , `/posts/ ${ id } ` , data ) }, deletePost ( id ) { return this . execute ( ' delete ' , `/posts/ ${ id } ` ) }
You lot now have all the components required to wire upward your posts manager component to brand Grime operations via the Residuum API. Open up ./src/components/PostsManager.vue
and copy/paste the following lawmaking.
<template> <div grade= "container-fluid mt-four" > <h1 class= "h1" >Posts Director</h1> <b-warning :prove= "loading" variant= "info" >Loading...</b-alert> <b-row> <b-col> <table grade= "table table-striped" > <thead> <tr> <th>ID</th> <th>Title</thursday> <th>Updated At</thursday> <thursday> </th> </tr> </thead> <tbody> <tr v-for= "post in posts" :key= "post.id" > <td>{{ post.id }}</td> <td>{{ mail.title }}</td> <td>{{ mail.updatedAt }}</td> <td class= "text-correct" > <a href= "#" @ click.forbid= "populatePostToEdit(post)" >Edit</a> - <a href= "#" @ click.prevent= "deletePost(post.id)" >Delete</a> </td> </tr> </tbody> </table> </b-col> <b-col lg= "three" > <b-card :title= "(model.id ? 'Edit Post ID#' + model.id : 'New Post')" > <grade @ submit.foreclose= "savePost" > <b-form-grouping label= "Championship" > <b-form-input type= "text" five-model= "model.title" ></b-form-input> </b-form-group> <b-form-group label= "Torso" > <b-form-textarea rows= "iv" v-model= "model.body" ></b-form-textarea> </b-grade-group> <div> <b-btn blazon= "submit" variant= "success" >Relieve Mail service</b-btn> </div> </form> </b-card> </b-col> </b-row> </div> </template> <script> import api from ' @/api ' export default { data () { render { loading : false , posts : [], model : {} } }, async created () { this . refreshPosts () }, methods : { async refreshPosts () { this . loading = true this . posts = await api . getPosts () this . loading = faux }, async populatePostToEdit ( post ) { this . model = Object . assign ({}, post ) }, async savePost () { if ( this . model . id ) { look api . updatePost ( this . model . id , this . model ) } else { expect api . createPost ( this . model ) } this . model = {} // reset form await this . refreshPosts () }, async deletePost ( id ) { if ( ostend ( ' Are you sure y'all desire to delete this post? ' )) { // if we are editing a post we deleted, remove it from the form if ( this . model . id === id ) { this . model = {} } look api . deletePost ( id ) look this . refreshPosts () } } } } </script>
Listing Posts
Y'all'll use api.getPosts()
to fetch posts from your REST API server. Yous should refresh the listing of posts when the component is loaded and later on any mutating performance (create, update, or delete).
async refreshPosts () { this . loading = true this . posts = await api . getPosts () this . loading = false }
The attribute this.loading
is toggled and so the UI tin can reflect the awaiting API call. You might not meet the loading message since the API request is not going out to the net.
Creating Posts
A course is included in the component to save a post. It's wired up to phone call savePosts()
when the form is submitted and its inputs are jump to the model
object on the component.
When savePost()
is called, it will perform either an update or create based on the existence of model.id
. This is by and large a shortcut to not take to define ii separate forms for creating and updating.
async savePost () { if ( this . model . id ) { await api . updatePost ( this . model . id , this . model ) } else { expect api . createPost ( this . model ) } this . model = {} // reset form await this . refreshPosts () }
Updating Posts
When updating a post, you first must load the post into the form. This sets model.id
which will the trigger an update in savePost()
.
async populatePostToEdit ( post ) { this . model = Object . assign ({}, post ) }
Important: The Object.assign()
call copies the value of the post argument rather than the reference. When dealing with mutation of objects in Vue, you should always gear up to the value, not reference.
Deleting Posts
To delete a post but call api.deletePost(id)
. It's always good to ostend before delete so let'south throw in a native confirmation alert box to make sure the click was intentional.
async deletePost ( id ) { if ( confirm ( ' Are you lot sure you want to delete this post? ' )) { await api . deletePost ( id ) await this . refreshPosts () } }
Test Your Vue.js + Node CRUD App
Make sure both the server and frontend are running.
Terminal #1
Terminal #two
Navigate to http://localhost:8080
and give information technology a whirl.
Exercise More With Vue!
Every bit I said at the top of this post, I think Vue stands head and shoulders above other frameworks. Here are five quick reasons why:
- Uncomplicated component lifecycle
- HTML-based templating and native ii-manner binding
- Widely agreed upon ways to handle routing, state management, webpack configuration, and isomorphic spider web apps
- Massive community supported resource, components, libraries, and projects
- Vue feels very similar to React (without the JSX!) which lowers the barrier to entry for those with React feel. Moving betwixt React and Vue isn't very difficult.
I covered a lot of material in this tutorial just don't feel bad if you lot didn't grasp everything the first time. The more you work with these technologies, the more familiar they volition go.
To learn more about Vue.js head over to https://vuejs.org or check out these other great resources from the @oktadev team:
- The Ultimate Guide to Progressive Web Applications
- The Lazy Developer's Guide to Authentication with Vue.js
- Build a Cryptocurrency Comparison Site with Vue.js
You can detect the source code for the application adult in this post at https://github.com/oktadeveloper/okta-vue-node-instance.
Hitting me up in the comments with any questions, and equally always, follow @oktadev on Twitter to see all the absurd content our dev team is creating.
deffellstraindich.blogspot.com
Source: https://developer.okta.com/blog/2018/02/15/build-crud-app-vuejs-node
0 Response to "File Exists: /node_modules/.bin/node-pre-gyp Move It Away"
Post a Comment