Using VUEX necessary with NODE.js REST Backend - rest

I´m not very experienced with Frontend/Backend Architecture, but i created a simple REST Backend with NODE.js and want to build up a Frontend based on Vue.js and Framework7.
So do you recommend using VUEX there? Or how do you deal with the sessions or the different requests you sending to the Backend?
Thanks a lot!

You don't have to use Vuex, but I'd suggest using Vuex. Here's an example using Vuex and rest api.
In store/actions.js
import {
fetchSomething,
} from '../api/index.js';
export const actions = {
getSomething({ commit }) {
fetchSomething().then((something) => {
commit('UPATED_SOMETHING', something);
});
},
}
In api/index.js
export const fetchSomething = () => {
const url = 'Some endpoint';
return new Promise((resolve) => {
axios.get(url).then((res) => {
const data = res.data;
resolve(data);
}).catch((err) => {
console.log(err);
})
})
}
In store/mutations.js
export const mutations = {
UPATED_SOMETHING(state, data) {
state.something = data;
},
}
In store/index.js
import { getters } from './getters'
import { actions } from './actions'
import { mutations } from './mutations'
// initial state
const state = {
something: null,
}
export default {
state,
getters,
actions,
mutations,
}
In store/getters.js
export const getters = {
getSomething: state => {
return state.something;
},
}

Related

Issue testing RTK Query: The preloadedState argument passed to createStore has unexpected type of "array"

I'm learning RTK Query.
So, in a test, I have the following error:
The preloadedState argument passed to createStore has unexpected type of "array". Expected argument to be an object with the following keys: "queries", "mutations", "provided", "subscriptions", "config"
This is my test:
test("Can use preloadedState", () => {
const initialPosts = [
{
id: 1,
body: "Lorem ipsum",
},
];
// wrap component with custom render function
renderWithProviders(<GenericList />, {
preloadedState: {
postsSlice: initialPosts,
},
});
const loremIpsum = screen.getByText(/lorem ipsum/i);
expect(loremIpsum).toBeInTheDocument();
});
I have followed this tutorial https://redux.js.org/usage/writing-tests#preparing-initial-test-state.
This is my test-utils.js file:
import React from 'react'
import { render } from '#testing-library/react'
import { Provider } from 'react-redux'
import { setupStore } from '../../store/index'
import { setupListeners } from '#reduxjs/toolkit/dist/query'
export function renderWithProviders(
ui,
{
preloadedState = {},
// Automatically create a store instance if no store was passed in
store = setupStore(preloadedState),
...renderOptions
} = {}
) {
setupListeners(store.dispatch);
function Wrapper({ children }) {
return <Provider store={store}>{children}</Provider>
}
return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) }
}
This is my store:
import { configureStore } from "#reduxjs/toolkit";
import { postsSlice } from "../features/postsSlice";
export const setupStore = preloadedState => {
return configureStore({
reducer: {
[postsSlice.reducerPath]: postsSlice.reducer,
},
preloadedState,
middleware: getDefaultMiddleware =>
getDefaultMiddleware({
immutableCheck: false,
serializableCheck: false,
}).concat(postsSlice.middleware),
})
}
And finally this is the postsSlice
import { createApi, fetchBaseQuery } from "#reduxjs/toolkit/query/react";
export const postsSlice = createApi({
// Reducer Path it's name shown on Redux Tab
reducerPath: "postsSlice",
baseQuery: fetchBaseQuery({
baseUrl: process.env.REACT_APP_BACKEND_URL,
}),
// With tag type we can invalidate cache
tagTypes: ['posts'],
endpoints: (builder) => ({
getPosts: builder.query({
query: () => "/posts"
})
})
});
export const { useGetPostsQuery } = postsSlice;
You cannot just make up some random contents for your initialState, it has to be exactly the structure of your Redux state. And for RTK Query, that is a very complex internal structure that you should probably not mock (it could change in another version!).
Honestly, to your last question, this is a step backwards - if you want to test RTK Query, test it with a normal Redux store and mock the api.
All you were missing was to wait in your test until the result was rendered.
Faking internal data structures means that your test will just test a very small part of what actually happens.

Call api with axios since my component and my store

I'm new to Vue 3 (cli) and I'm not at all comfortable with front-end technology, so I'm having a hard time understanding the information I'm reading.
I succeeded in creating a registration/login interface with an api and JWT. The user information needs to be persisted everywhere in the project I'm doing to train myself, so I configured axios in my store.
store/index.js
import { createStore } from 'vuex'
import axios from 'axios';
const api = axios.create({
baseURL: 'http://127.0.0.1:7000'
});
let user = localStorage.getItem('user');
if(null === user) {
user = {uuid: '', token: ''};
} else {
try {
user = JSON.parse(user);
api.defaults.headers.common['Authorization'] = 'Bearer ' + user.token;
} catch (e) {
user = {uuid: '', token: ''};
}
}
export default createStore({
state: {
status: '',
user: user,
userInfos: {},
},
mutations: {
[...]
},
getters: {
},
actions: {
[...]
},
modules: {
}
})
I would like to be able to use api from my components. I have had several approaches:
1 - I have imported axios into my component, but this is not correct at all, as I will need axios in all my components.
2 - I've looked at different documentations that explain how to configure axios globally, but no two are the same and I couldn't get anything to work.
3 - I've tried calling api through strangenesses like this.$store.api in my methods, but obviously this is abused.
Can anyone help me understand what is the right way to use axios from my components and from the store with only one configuration? Knowing that I need to be able to keep my headers up to date for authentication with the Bearer Token (a mutation updates it in the store at user login).
main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap/dist/js/bootstrap.js'
import { library } from '#fortawesome/fontawesome-svg-core'
import { FontAwesomeIcon } from '#fortawesome/vue-fontawesome'
import { faMedal } from '#fortawesome/free-solid-svg-icons'
import { faLaptopMedical } from '#fortawesome/free-solid-svg-icons'
import { faCookieBite } from '#fortawesome/free-solid-svg-icons'
import { faCoins } from '#fortawesome/free-solid-svg-icons'
import { faHourglassStart } from '#fortawesome/free-solid-svg-icons'
import { faUpRightFromSquare } from '#fortawesome/free-solid-svg-icons'
import { faInfo } from '#fortawesome/free-solid-svg-icons'
import { faGears } from '#fortawesome/free-solid-svg-icons'
library.add(
faMedal,
faCoins,
faLaptopMedical,
faCookieBite,
faHourglassStart,
faUpRightFromSquare,
faInfo,
faGears
);
createApp(App)
.component('font-awesome-icon', FontAwesomeIcon)
.use(store)
.use(router)
.mount('#app')
Thank you very much for your help.
If you're creating a new app, I would use Pinia, which is really the next version of VueX. Don't put the user in localStorage, but in a store that you can access from all views and components.
So Axios setup in composables/myaxiosfile.js
// src/stores/oneStore.js
import { defineStore } from "pinia";
// Possibly import and deconstruct functions from #/api.js and use
// those functions in the "actions" section of the store,
// updating the state according to the answer of the api call.
export const useOneStore = defineStore("oneStore", {
state: () => {
return {
user: true
}
}
// actions
// getters
})
and in a component :
import { useOneStore } from '../stores/oneStore';
const oneStore = useOneStore()
I don't know if this is the right way, but by doing so, it allows me to use the store api in my components.
store/index.js
state: {
api: {},
[...]
},
mutations: {
setApi: function (state, api) {
state.api = api;
},
connexionUser: function (state, user) {
state.user = user;
api.defaults.headers.common['Authorization'] = 'Bearer ' + user.token;
state.api = api;
},
[...]
},
actions: {
setApi: ({commit}) => {
commit('setApi', api);
},
[...]
},
App.vue
mounted() {
this.$store.dispatch('setApi');
[...]
}
Like this, offline, it loads api which is set at the top of my store (see in my question) and when I log in, I update api in state to have JWT authentication.

how to use env variables in nuxt 3 outside of setup scripts

So the problem is that I would like to use Axios instance. Because:
new useFetch is only possible to use inside of components aka setup scrips. https://v3.nuxtjs.org/guide/features/data-fetching/
community axios module is only possible inside of nuxt2 https://github.com/nuxt-community/axios-module/issues/536 and are nor supported in nuxt3
I need to make calls in pinia actions(store) to my backend service.
nuxt.config.js
import { defineNuxtConfig } from "nuxt";
export default defineNuxtConfig({
runtimeConfig: {
public: {
apiBase: process.env.API_BASE_URL ?? "http://localhost:8080/api/v1",
},
},
env: {
apiBase: process.env.API_BASE_URL ?? "http://localhost:8080/api/v1",
},
buildModules: ["#pinia/nuxt"],
});
and here is instance.js
import axios, { AxiosResponse } from "axios";
const instance = axios.create({
baseURL: process.env.API_BASE_URL,
});
instance.interceptors.response.use((response: AxiosResponse) => {
return response.data;
});
export default instance;
So it does see the envs on server-side as I can console log them but on client I do receive can't read of undefined
You can access your env variables using a composable and the useRuntimeConfig method.
Something like this for instance:
// file composables/use-axios-instance.ts
import axios, { AxiosResponse } from "axios";
let instance = null;
export const useAxiosInstance = () => {
const { API_BASE_URL } = useRuntimeConfig();
if (!instance) {
instance = axios.create({
baseURL: API_BASE_URL,
});
instance.interceptors.response.use((response: AxiosResponse) => {
return response.data;
});
}
return instance;
};
Then you can access to your axios instance using const axios = useAxiosInstance();

Is there a way to detect server side cookie for all pages in nextjs? [duplicate]

So I'm creating authentication logic in my Next.js app. I created /api/auth/login page where I handle request and if user's data is good, I'm creating a httpOnly cookie with JWT token and returning some data to frontend. That part works fine but I need some way to protect some pages so only the logged users can access them and I have problem with creating a HOC for that.
The best way I saw is to use getInitialProps but on Next.js site it says that I shouldn't use it anymore, so I thought about using getServerSideProps but that doesn't work either or I'm probably doing something wrong.
This is my HOC code:
(cookie are stored under userToken name)
import React from 'react';
const jwt = require('jsonwebtoken');
const RequireAuthentication = (WrappedComponent) => {
return WrappedComponent;
};
export async function getServerSideProps({req,res}) {
const token = req.cookies.userToken || null;
// no token so i take user to login page
if (!token) {
res.statusCode = 302;
res.setHeader('Location', '/admin/login')
return {props: {}}
} else {
// we have token so i return nothing without changing location
return;
}
}
export default RequireAuthentication;
If you have any other ideas how to handle auth in Next.js with cookies I would be grateful for help because I'm new to the server side rendering react/auth.
You should separate and extract your authentication logic from getServerSideProps into a re-usable higher-order function.
For instance, you could have the following function that would accept another function (your getServerSideProps), and would redirect to your login page if the userToken isn't set.
export function requireAuthentication(gssp) {
return async (context) => {
const { req, res } = context;
const token = req.cookies.userToken;
if (!token) {
// Redirect to login page
return {
redirect: {
destination: '/admin/login',
statusCode: 302
}
};
}
return await gssp(context); // Continue on to call `getServerSideProps` logic
}
}
You would then use it in your page by wrapping the getServerSideProps function.
// pages/index.js (or some other page)
export const getServerSideProps = requireAuthentication(context => {
// Your normal `getServerSideProps` code here
})
Based on Julio's answer, I made it work for iron-session:
import { GetServerSidePropsContext } from 'next'
import { withSessionSsr } from '#/utils/index'
export const withAuth = (gssp: any) => {
return async (context: GetServerSidePropsContext) => {
const { req } = context
const user = req.session.user
if (!user) {
return {
redirect: {
destination: '/',
statusCode: 302,
},
}
}
return await gssp(context)
}
}
export const withAuthSsr = (handler: any) => withSessionSsr(withAuth(handler))
And then I use it like:
export const getServerSideProps = withAuthSsr((context: GetServerSidePropsContext) => {
return {
props: {},
}
})
My withSessionSsr function looks like:
import { GetServerSidePropsContext, GetServerSidePropsResult, NextApiHandler } from 'next'
import { withIronSessionApiRoute, withIronSessionSsr } from 'iron-session/next'
import { IronSessionOptions } from 'iron-session'
const IRON_OPTIONS: IronSessionOptions = {
cookieName: process.env.IRON_COOKIE_NAME,
password: process.env.IRON_PASSWORD,
ttl: 60 * 2,
}
function withSessionRoute(handler: NextApiHandler) {
return withIronSessionApiRoute(handler, IRON_OPTIONS)
}
// Theses types are compatible with InferGetStaticPropsType https://nextjs.org/docs/basic-features/data-fetching#typescript-use-getstaticprops
function withSessionSsr<P extends { [key: string]: unknown } = { [key: string]: unknown }>(
handler: (
context: GetServerSidePropsContext
) => GetServerSidePropsResult<P> | Promise<GetServerSidePropsResult<P>>
) {
return withIronSessionSsr(handler, IRON_OPTIONS)
}
export { withSessionRoute, withSessionSsr }

redux observable with axios onProgress

i am creating an upload function that will show a progress bar to the client inside a React Redux and Redux-observable, and i use axios to do a put request to AWS S3.
My epics is as follow
...
function uploadFile(mimetype, url, file) {
const config = {
headers: {
'Content-Type': mimetype,
},
onUploadProgress(progress) {
const percentCompleted = Math.round((progress.loaded * 100) / progress.total)
uploadProgress(percentCompleted)
},
}
axiosRetry(axios, { retries: 3 })
return axios.put(url, file[0], config)
}
export const uploadEpic = (action$, store) => action$
.ofType(SIGNED_URL_SUCCESS)
.mergeMap(() => {
const file = store.getState().File.droppedFile
const mimetype = file[0].type
const { url } = store.getState().SignedUrl
const { fileData } = store.getState().Upload
return of(uploadFile(mimetype, url.data, file))
.concatMap(() => {
const uploadedData = {
url: fileData.url,
thumbUrl: `${fileData.folder}/${fileData.filename}-00001.png`,
}
return [
upload(uploadedData),
uploadSuccess(),
]
})
.catch(error => of(uploadFailure(error)))
})
export default uploadEpic
The upload seems to work, as i received an AWS SNS email telling that its done, but i can't seem to see that it is updating the Upload.progress state inside my Upload reducer.
The reason i am using axios is particulary because its axios-retry and its onUploadProgress, since i can't seem to find an example doing an onProgress using universal-rx-request
so two questions probably
How can i achieve this using axios
How can i achieve this using universal-rx-request
Thanks to this SO answer
I ended up not using axios at all
I got it working with this
import { of } from 'rxjs/observable/of'
import { Subject } from 'rxjs/Subject'
import { Observable } from 'rxjs/Observable'
import 'rxjs/add/observable/dom/ajax'
import { SIGNED_URL_SUCCESS } from 'ducks/SignedUrl'
import {
upload,
uploadIsLoading,
uploadSuccess,
uploadFailure,
uploadProgress,
} from 'ducks/Upload'
export const uploadEpic = (action$, store) => action$
.ofType(SIGNED_URL_SUCCESS)
.mergeMap(() => {
const file = store.getState().File.droppedFile
const mimetype = file[0].type
const { url } = store.getState().SignedUrl
const { fileData } = store.getState().Upload
const progressSubscriber = new Subject()
const request = Observable.ajax({
method: 'PUT',
url: url.data,
body: file[0],
headers: {
'Content-Type': mimetype,
},
progressSubscriber,
})
const requestObservable = request
.concatMap(() => {
const uploadedData = {
...
}
return [
upload(uploadedData),
uploadIsLoading(false),
uploadSuccess(),
]
})
.catch(error => of(uploadFailure(error)))
return progressSubscriber
.map(e => ({ percentage: (e.loaded / e.total) * 100 }))
.map(data => uploadProgress(data.percentage))
.merge(requestObservable)
})
UPDATE: on rxjs 6 the merge operators is deprecated, so if you're using rxjs 6, change the code above to
// some/lib/folder/uploader.js
import { of, merge } from 'rxjs' // import merge here
import { ajax } from 'rxjs/ajax'
import { map, catchError } from 'rxjs/operators' // instead of here
import { Subject } from 'rxjs/Subject'
export function storageUploader(...args) {
const progressSubscriber = new Subject()
const request = ajax({...someRequestOptions})
.pipe(
map(() => success()),
catchError((error) => of(failure(error))),
)
const subscriber = progressSubscriber
.pipe(
map((e) => ({ percentage: (e.loaded / e.total) * 100 })),
map((upload) => progress(upload.percentage)),
catchError((error) => of(failure(error))),
)
return merge(subscriber, request) // merge both like this, instead of chaining the request on progressSubscriber
}
//the_epic.js
export function uploadEpic(action$, state$) {
return action$
.pipe(
ofType(UPLOAD),
mergeMap((someUploadOptions) => uploaderLib(
{ ...someUploadOptions },
actionSuccess,
actionFailure,
actionProgress,
)),
catchError((error) => of(actionFailure(error))),
)
}