
Sunday, August 9, 2020
We rocked in our previous posts, where we successfully built a backend for our music streaming app using Django, Django rest framework and enhanced security using JWT. In this, we will be using vuejs to build a beautiful frontend for our music API. However, this tutorial should work for any backend you built. Let's not make this posting boring with traditional UI instead, let's try to make our music streaming app look professional. we will be using vuetify for this, a material design UI framework for vuejs.
Let's get started
Let's create our homepage so that the user can register and login.
Now we handle our user state using vuex
Let's add beautiful navbar in views/App.vue
Let's create a signup vue in views
Now create our songs component in Components
However, this needs lots of improvement like building up a music player instead of a native media player, playlist etc.. Stay connected with this blog we will make updates.
Finally, let's make routes to pages using vue router
Yay! we have built a music streaming app entirely from scratch using Django, DRF and vuejs. Thanks for your huge support! As always proud to be a Dev 👨💻 .
Kindly share this post with your fellow Devs!
Let's get started
vue create ronix //using vue_cliSelect manual preset and select vue router, vuex, Babel
cd ronix
vue add vuetify //adding vuetify using Vue CLIVuex documentation says " Vuex is a state management pattern + library for Vue.js applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion". For us, we do not want our users to login every time. So we use vuex to manage user's state whether he/she is logged in or not according to the life cycle of jwt.
Let's create our homepage so that the user can register and login.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template lang="html" class="primary_bg"> | |
<div class="primary_bg"> | |
<v-container fluid class="mb-15"> | |
<v-layout row> | |
<v-flex md6 lg6 sm12 xs12 x6> | |
<img src="../assets/Chilling-at-Home/main_files/chilling.svg" width="95%"> | |
</v-flex> | |
<v-flex md6 lg6 sm12 xs12 x6 pl-3> | |
<h1 class="white--text font-weight-medium mt-16 d-sm-pl-8 d-xs-pl-6">Roni<span class="red--text">X</span></h1> | |
<h2 class="white--text d-sm-pl-6">Discover great music <span class="yellow--text">that rules out </span></h2> | |
<p class="white--text d-sm-pl-6">Music is eternal that has limitless potential in grasping and treating of great forces on the earth.However to get started login to your existing account or create one if you dont have.</p> | |
<v-dialog v-model="dialog" scrollable max-width="300px"> | |
<!-- Login popup modal --> | |
<template v-slot:activator="{ on, attrs }"> | |
<v-btn | |
color="black--text white" | |
dark | |
v-bind="attrs" | |
v-on="on" | |
> | |
Login | |
</v-btn> | |
</template> | |
<v-card> | |
<v-card-title>Login</v-card-title> | |
<v-divider></v-divider> | |
<v-card-text class="mt-2"> | |
<v-text-field | |
v-model="email" | |
solo | |
label="Email" | |
prepend-inner-icon="mdi-email" | |
></v-text-field> | |
<!-- Allowing user to toggle visibility of password --> | |
<v-text-field | |
v-model="password" | |
:type="show1 ? 'text' : 'password'" | |
name="input-10-1" | |
@click:append="show1 = !show1" | |
solo | |
:append-icon="show1 ? 'mdi-eye' : 'mdi-eye-off'" | |
label="Password" | |
prepend-inner-icon="mdi-account-key" | |
></v-text-field> | |
</v-card-text> | |
<v-divider></v-divider> | |
<v-card-actions> | |
<v-btn color="blue darken-1" text @click="dialog = false">Close</v-btn> | |
<v-btn color="blue darken-1" text @click.prevent="authenticate">login</v-btn> | |
</v-card-actions> | |
</v-card> | |
</v-dialog> | |
<v-btn class="ml-2" outlined color="white"> <router-link to="/signup" tag='span'>Create account</router-link> </v-btn> | |
<p><v-chip | |
color="black" | |
text-color="white" | |
class="mt-12" | |
> | |
<v-avatar | |
left | |
class="red darken-4" | |
> | |
<v-icon size="16">mdi-newspaper-plus</v-icon> | |
</v-avatar> | |
Updated with Private Music streams > | |
</v-chip></p> | |
</v-flex> | |
</v-layout> | |
</v-container> | |
<v-footer | |
color="primary_bg" | |
padless | |
> | |
<v-row | |
justify="center" | |
no-gutters | |
> | |
<v-col | |
class="text-center white--text" | |
cols="12" | |
> | |
{{ new Date().getFullYear() }} — <strong>Ronix music</strong> | |
<p>Glammingspace</p> | |
</v-col> | |
</v-row> | |
</v-footer> | |
</div> | |
</template> | |
<script> | |
import axios from 'axios' | |
export default { | |
data () { | |
return { | |
show1: false, | |
email: '', | |
password: '', | |
dialog: false, | |
} | |
}, | |
methods: { | |
authenticate () { | |
const payload = { | |
email: this.email, | |
password: this.password | |
} | |
axios.post(this.$store.state.endpoints.obtainJWT, payload) | |
.then((response) => { | |
this.$store.commit('updateToken', response.data.token) | |
// get and set auth user | |
const base = { | |
baseURL: this.$store.state.endpoints.baseUrl, | |
headers: { | |
// Set your Authorization to 'JWT' !! | |
Authorization: `JWT ${this.$store.state.jwt}`, | |
'Content-Type': 'application/json' | |
}, | |
xhrFields: { | |
withCredentials: true | |
} | |
} | |
const axiosInstance = axios.create(base) | |
axiosInstance({ | |
url: "/rest-auth/user/", | |
method: "get", | |
params: {} | |
}) | |
.then((response) => { | |
this.$store.commit("setAuthUser", | |
{authUser: response.data, isAuthenticated: true} | |
) | |
this.$router.push({name: 'Songs'}) | |
}) | |
}) | |
.catch((error) => { | |
console.log(error); | |
console.debug(error); | |
console.dir(error); | |
}) | |
} | |
} | |
} | |
</script> | |
<style lang="css" scoped> | |
.primary_bg{ | |
background-color:#9921e8; | |
background-image:linear-gradient(315deg, #9921e8 0%, #5f72be 74%); | |
} | |
.v-text-field{ | |
width: 400px; | |
} | |
</style> |
Now we handle our user state using vuex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//add to store/index.js | |
import Vue from 'vue' | |
import Vuex from 'vuex' | |
import axios from 'axios' | |
Vue.use(Vuex) | |
// Make Axios good with django csrf | |
axios.defaults.xsrfCookieName = 'csrftoken' | |
axios.defaults.xsrfHeaderName = 'X-CSRFToken' | |
export default new Vuex.Store({ | |
state: { | |
authUser: {}, | |
isAuthenticated: false, | |
jwt: localStorage.getItem('token'), | |
APIData:'', | |
endpoints: { | |
// Change these with your endpoints. | |
obtainJWT: 'https://127.0.0.1/api/v1/auth/obtain_token/', | |
refreshJWT: 'https://127.0.0.1/api/v1/auth/refresh_token/', | |
baseUrl: 'https://127.0.0.1/' | |
} | |
}, | |
mutations: { | |
setAuthUser(state, { | |
authUser, | |
isAuthenticated | |
}) { | |
Vue.set(state, 'authUser', authUser) | |
Vue.set(state, 'isAuthenticated', isAuthenticated) | |
}, | |
//You should find a better alternative to storing in local storage | |
updateToken(state, newToken) { | |
localStorage.setItem('token', newToken); | |
state.jwt = newToken; | |
}, | |
removeToken(state) { | |
localStorage.removeItem('token'); | |
state.jwt = null; | |
} | |
} | |
}) |
Let's add beautiful navbar in views/App.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<v-app> | |
<v-app-bar | |
app | |
color="primary_bg" | |
dark | |
dense | |
> | |
<div class="d-flex align-center"> | |
<v-toolbar-title>Ronix</v-toolbar-title> | |
</div> | |
<v-spacer></v-spacer> | |
<v-btn icon> | |
<router-link to="/" tag="icon"><v-icon>mdi-home</v-icon></router-link> | |
</v-btn> | |
<v-btn icon> | |
<router-link to="/songs" tag="icon"><v-icon>mdi-playlist-music</v-icon></router-link> | |
</v-btn> | |
<v-btn icon> | |
<v-icon>mdi-magnify</v-icon> | |
</v-btn> | |
<v-btn icon> | |
<v-icon>mdi-account-circle</v-icon> | |
</v-btn> | |
</v-app-bar> | |
<v-content> | |
<router-view></router-view> | |
</v-content> | |
</v-app> | |
</template> | |
<script> | |
export default { | |
name: 'App', | |
components: { | |
}, | |
data: () => ({ | |
links: [ | |
'Home', | |
'About Us', | |
'Team', | |
'Services', | |
'Blog', | |
'Contact Us', | |
], | |
}), | |
}; | |
</script> | |
<style lang="css"> | |
.primary_bg{ | |
background-color:#9921e8; | |
background-image:linear-gradient(315deg, #9921e8 0%, #5f72be 74%); | |
} | |
</style> |
Let's create a signup vue in views
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!--add to views/signup.vue--> | |
<template lang="html"> | |
<v-form> | |
<v-container> | |
<v-row> | |
<v-col cols="12" sm="12" md="12" lg="6"> | |
<p class="mx-auto" justify-center>Login</p> | |
<v-text-field | |
v-model="email" | |
solo | |
label="Email" | |
prepend-inner-icon="mdi-email" | |
></v-text-field> | |
<v-text-field | |
v-model="password1" | |
:type="show1 ? 'text' : 'password'" | |
name="input-10-1" | |
@click:append="show1 = !show1" | |
solo | |
:append-icon="show1 ? 'mdi-eye' : 'mdi-eye-off'" | |
label="Password" | |
prepend-inner-icon="mdi-lock" | |
></v-text-field> | |
<v-text-field | |
v-model="password2" | |
:type="show2 ? 'text' : 'password'" | |
name="input-10-1" | |
@click:append="show2 = !show2" | |
solo | |
:append-icon="show2 ? 'mdi-eye' : 'mdi-eye-off'" | |
label="Repeat Password" | |
prepend-inner-icon="mdi-lock-reset" | |
></v-text-field> | |
<v-btn | |
:loading="loading3" | |
:disabled="loading3" | |
color="blue-grey" | |
class="ma-2 white--text" | |
@click.prevent="register" | |
> | |
signup | |
<v-icon right dark>mdi-account</v-icon> | |
</v-btn> | |
<p class="ma-3 ma-md-1">Have an account ?</p> | |
<v-btn> <router-link to="/" tag='span'>Login</router-link> </v-btn> | |
</v-col> | |
</v-row> | |
</v-container> | |
</v-form> | |
</template> | |
<script> | |
import axios from 'axios' | |
export default { | |
data () { | |
return { | |
email: '', | |
password1: '', | |
password2: '', | |
} | |
}, | |
methods: { | |
register () { | |
const payload = { | |
email: this.email, | |
password1: this.password1, | |
password2:this.password2 | |
} | |
axios.post('https://127.0.0.1/rest-auth/registration/', payload) | |
} | |
} | |
} | |
</script> | |
<style lang="css"> | |
</style> |
Now create our songs component in Components
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- Components/Songs.vue--> | |
<template> | |
<v-container> | |
<v-layout wrap> | |
<v-flex xs4 md2 | |
v-for="(item, index) in posts" | |
:key="index" | |
mb-16> | |
<v-card | |
class="mx-auto" | |
max-width="100" | |
max-height="90" | |
> | |
<v-img | |
class="white--text align-end rounded-lg" | |
height="110px" | |
width="120px" | |
:src="item.album" | |
> | |
<!--playing using native audio player--> | |
<audio :src="item.song" controls></audio> | |
</v-img> | |
<!-- <audio ref="audiofile" :src="item.song" controls></audio> --> | |
<div class="overflow_prevent">{{ item.title}}</div> | |
<div class="caption overflow_prevent" style="line-height: 100%;">{{ item.artist}}</div> | |
</v-card> | |
</v-flex> | |
</v-layout> | |
</v-container> | |
</template> | |
<script> | |
import axios from 'axios' | |
export default { | |
data() { | |
return { | |
posts: [], | |
errors: [], | |
file: '', | |
multiLine: true, | |
snackbar: true, | |
} | |
}, | |
methods:{ | |
play() { | |
console.log('playing') | |
this.audio.play(); | |
} | |
}, | |
created() { | |
this.audio = document.getElementById('audio'); | |
}, | |
mounted() { | |
const base = { | |
baseURL: this.$store.state.endpoints.baseUrl, | |
headers: { | |
// Set your Authorization to 'JWT'!!! | |
Authorization: `JWT ${this.$store.state.jwt}`, | |
'Content-Type': 'application/json' | |
}, | |
xhrFields: { | |
withCredentials: true | |
} | |
} | |
const axiosInstance = axios.create(base) | |
axiosInstance({ | |
url: "https://127.0.0.1/api/v1/songs/", | |
method: "get", | |
params: {} | |
}) | |
.then((response) => { | |
this.posts = response.data.results | |
}) | |
.catch(e => { | |
this.errors.push(e) | |
}) | |
} | |
} | |
</script> | |
<style lang="css"> | |
.overflow_prevent{ | |
white-space: nowrap; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
} | |
</style> |
However, this needs lots of improvement like building up a music player instead of a native media player, playlist etc.. Stay connected with this blog we will make updates.
Finally, let's make routes to pages using vue router
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Vue from 'vue' | |
import VueRouter from 'vue-router' | |
import Signup from '../views/Signup.vue' | |
import Portfolio from '@/components/Portfolio'; | |
import Songs from '@/components/Songs' | |
Vue.use(VueRouter) | |
const routes = [ | |
{ | |
path: '/', | |
name: 'Home', | |
component: Portfolio | |
}, | |
{ | |
path: '/signup', | |
name: 'Signup', | |
component: Signup | |
}, | |
{ | |
path: '/songs', | |
name: 'Songs', | |
component: Songs | |
}, | |
/*{ | |
path: '', | |
name: '', | |
component: , | |
}*/ | |
] | |
const router = new VueRouter({ | |
mode: 'history', | |
base: process.env.BASE_URL, | |
routes | |
}) | |
export default router |
Yay! we have built a music streaming app entirely from scratch using Django, DRF and vuejs. Thanks for your huge support! As always proud to be a Dev 👨💻 .
Kindly share this post with your fellow Devs!
Sunday, August 9, 2020
Nodejs
python
web development
This comment has been removed by a blog administrator.
ReplyDeleteIt's an art. Appreciate your work !
ReplyDelete