How to use 'env' property in Nuxt.js to get newapi? - javascript

I try to make news web apps to use newsapi.org.
I wanted to hide my api_key so I decided to use env property in Nuxt.Js.
But now I got 401 status code from server.
first of all, I made the .env file in project file and I put my API_KEY.
and then I installed 'dotenv' use 'yarn add dotenv' command in VSCode terminal.
and I add nuxt.config.ts file. I have used TypeScript in my project so all file depend on TypeScript.
require('dotenv').config()
const { API_KEY } = process.env
export default {
~~~~~~~~~~
env: {
API_KEY,
},
}
and I used Vuex to get news information.
so I made code like following.
~/store/getNews.ts
import { MutationTree, ActionTree, GetterTree } from "vuex";
import axios from "axios";
const url = 'http://newsapi.org/v2/top-headlines';
interface RootState { }
export interface NewsArticles {
source?: {}
author?: string
title?: string
description?: string
url?: any
urlToImage?: any
publishedAt?: string
content?: string
}
interface State {
newArticle: NewsArticles
}
export const state = () => ({
newsArticle: []
})
export const getters: GetterTree<State, RootState> = {
newsArticle: (state: State) => state.newArticle
}
export const mutations: MutationTree<State> = {
setNewsArticle: (state: State, newsArticle: NewsArticles) => {
state.newArticle = newsArticle
}
}
export const actions: ActionTree<State, RootState> = {
getNewsArticle: async ({ commit },{params}) => {
try{
const data = await axios.get(url,{params})
commit('setNewsArticle', data.data.articles)
}catch(error){
commit('setNewsArticle',[])
}
}
}
export default { state, getters, mutations, actions }
and finally, I made vue file to show the news information like following.
<template>
<div>
<p>this is NewsApi test pages!!</p>
<ul v-for="(item, index) in items" :key="index">
<li>{{ item.title }}</li>
</ul>
</div>
</template>
<script lang="ts">
import { Component, namespace, Vue } from 'nuxt-property-decorator'
import { NewsArticles } from '~/store/getNews'
const getNews = namespace('getNews')
#Component({})
export default class extends Vue {
#getNews.Action getNewsArticle!: Function
#getNews.Getter newsArticle!: NewsArticles
items: any = []
async mounted() {
await this.getNewsArticle({
params: { country: 'jp', category: 'business', apiKey: process.env.API_KEY },
})
this.items = this.newsArticle
}
}
</script>
I ran my app but I got 401 status code and I checked the console error like following.
{status: "error", code: "apiKeyInvalid",…}
code: "apiKeyInvalid"
message: "Your API key is invalid or incorrect. Check your key, or go to https://newsapi.org to create a free API key."
status: "error"
I don't know why that error occurred.
I checked apikey correctly setting to confirm consle.log.
in index.vue
<script lang='ts'>
~~~~
export default class extend Vue{
mounted(){
console.log(process.env.API_KEY)
}
}
</script>

You don't need to call require('dotenv').config(), as Nuxt automatically invokes it.
Also, for the env vars to be available in the production build, their names must be prefixed with NUXT_ENV_ (i.e., NUXT_ENV_API_KEY). Note this allows you to keep the key from being checked into source (assuming your .env file is also kept out of source control), but your API key can still be observed in the Network tab in DevTools.

Related

How can I correctly use the pinia store located in composables directory in my Nuxt.js app with Vue Composition API?

I am trying to use the pinia store, defined in /composables/pinia.ts, in my App.vue file. When I try to use it like this: const userStore = useUserStore(), the app does not crash, but a component I created with the Iconify library (which shows icons) is getting disabled. This means that I am not using the definition correctly. What is the correct way of using the store without disabling my Iconify component?
Here is my user store defined in /composables/pinia.ts :
export const useUserStore = defineStore('userStore', {
state: () => ({
user: {} as User,
}),
getters: {},
actions: {},
})
Same thing also happens when i try to use a function located in composables directory. It just removes the Icons in the webisite and does not even throws an error or anything. I assume that i am using wrong, that's why nuxt throws an error and stop rendering the icons. In addition here is my BaseIcon component:
<template lang="pug">
Icon(:icon="props.name" :style="styles")
</template>
<script setup lang="ts">
import { Icon, addCollection } from '#iconify/vue'
// #ts-ignore
const Icons = await import('#/assets/icons/icons.auto-generated.json')
addCollection(coolIcons)
interface Props {
name: string
size?: string
color?: string
}
const props = withDefaults(defineProps<Props>(), {
size: '1em',
color: 'currentColor',
})
const styles = ref({
fontSize: props.size,
color: props.color,
lineHeight: 1,
})
</script>

How to use Vue 3 Meta with Vue.js 3?

It seems that Vue Meta has been upgraded to handle Vue.js 3 with a new npm package called vue-3-meta
Before Vue.js 3, it was easy to use vue-meta by adding it to the Vue instance:
import Vue from 'vue'
import VueMeta from 'vue-meta'
Vue.use(VueMeta, {
// optional pluginOptions
refreshOnceOnNavigation: true
})
However in Vue.js 3, there is no Vue instance; and instead you create the app by running createApp like such:
const app = createApp(App);
const router = createVueRouter();
app.use(router);
// need to make app use Vue-Meta here
I cannot find any documentation for vue-3-meta. import VueMeta from 'vue-meta' no longer works.
How do I import the vue-3-meta plugin properly and use it with app like in prior versions?
Disclaimer: vue-meta v3 is still in alpha!
This was the minimal implementation I needed to get started:
Update vue-meta to v3 (in package.json)
- "vue-meta": "^2.4.0",
+ "vue-meta": "^3.0.0-alpha.7",
...or with yarn:
yarn add vue-meta#alpha
Add metaManager to Vue app
import { createMetaManager } from 'vue-meta'
const app = createApp(App)
.use(router)
.use(store)
.use(createMetaManager()) // add this line
await router.isReady()
app.mount('#app')
Add <metainfo> to App.vue <template> (this is also where I set a "title template")
<template>
<metainfo>
<template v-slot:title="{ content }">{{ content ? `${content} | SITE_NAME` : `SITE_NAME` }}</template>
</metainfo>
<header />
<router-view />
<footer />
</template>
Set default meta in App.vue <script>
Vue 3 vanilla:
import { useMeta } from 'vue-meta'
export default {
setup () {
useMeta({
title: '',
htmlAttrs: { lang: 'en', amp: true }
})
}
}
or with vue-class-component:
import { setup, Vue } from 'vue-class-component'
import { useMeta } from 'vue-meta'
export default class App extends Vue {
meta = setup(() => useMeta({
title: '',
htmlAttrs: { lang: 'en', amp: true }
})
}
Override meta in each component
Vue 3 vanilla:
import { useMeta } from 'vue-meta'
export default {
setup () {
useMeta({ title: 'Some Page' })
}
}
or with vue-class-component:
import { computed } from '#vue/runtime-core'
import { setup, Vue } from 'vue-class-component'
import { useMeta } from 'vue-meta'
export default class SomePage extends Vue {
meta = setup(() => useMeta(
computed(() => ({ title: this.something?.field ?? 'Default' })))
)
}
See also:
"Quick Usage" (vue-meta next branch)
Vue Router Example (vue-meta next branch)
In addition to the previous answers, I also needed to add a transpileDependency in my vue.config.js, as I was using vue-cli:
module.exports = {
transpileDependencies: ['vue-meta']
}
Else, I would get the error:
error in ./node_modules/vue-meta/dist/vue-meta.esm-browser.min.js
Module parse failed: Unexpected token (8:7170)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
Thanks to this thread for pointing me to this: https://stackoverflow.com/a/65844988/3433137
metaManager is a MetaManager instance created from createMetaManager() of vue-meta.
Based on the Vue 3 + Vue Router example for vue-meta, here's an example usage:
import { createApp } from 'vue'
import { createMetaManager, defaultConfig, resolveOption, useMeta } from 'vue-meta'
const decisionMaker5000000 = resolveOption((prevValue, context) => {
const { uid = 0 } = context.vm || {}
if (!prevValue || prevValue < uid) {
return uid
}
})
const metaManager = createMetaManager({
...defaultConfig,
esi: {
group: true,
namespaced: true,
attributes: ['src', 'test', 'text']
}
}, decisionMaker5000000)
useMeta(
{
og: {
something: 'test'
}
},
metaManager
)
createApp(App).use(metaManager).mount('#app')

refactor data service in Vue project

In a Vue project that's underway, the files which are responsible for communicating with the API have been grouped into a "services" section. While the code shown below works fine as written, it seems like simplifying it would be beneficial due to these factors:
there will be several more API specific files needed to support the project
the code in the 2 example API specific files shown below is essentially the same except for the value in the resource const
src/services/service.js
import axios from 'axios';
export default axios.create({
baseURL: 'advancedplanningextension/api/'
})
API specific files -
src/services/official-scenarios-service.js
import service from './service';
const resource = 'scenariolog';
export default {
get() {
return service.get(`${resource}`);
}
}
src/services/parameters-service.js
import service from './service';
const resource = 'parameter';
export default {
get() {
return service.get(`${resource}`);
}
}
file to choose the right API specific file -
src/services/service-factory.js
import OfficialScenariosService from './official-scenarios-service';
import ParametersService from './parameters-service';
const services = {
officialScenarios: OfficialScenariosService,
parameters: ParametersService
// add more here
}
export const ServiceFactory = {
get: name => services[name]
}
plugin file -
src/plugins/service.js
import { ServiceFactory } from '../services/service-factory';
export default {
install: function(Vue) {
Object.defineProperty(Vue.prototype, '$service', { value: ServiceFactory });
}
}
pertinent code in src/main.js that shows wiring of plugin file -
import ServicePlugin from './plugins/service';
Vue.use(ServicePlugin);
Example usage in src/views/official-scenarios.vue -
<template>
{{ officialScenarios }}
</template>
<script>
export default {
data() {
return {
officialScenarios: []
}
},
methods: {
async getOfficialScenarios() {
const { data } = await this.$service.get('officialScenarios').get();
this.officialScenarios = data;
}
},
mounted: function () {
this.getOfficialScenarios();
}
}
</script>
I tried creating a src/services/generic-service.js file (to replace the 2 API specific files), with the intent of calling that from src/services/service-factory.js, but quickly realized I didn't know how to populate the resource when it was called in that manner.
src/services/generic-service.js -
import service from './service';
let resource = ''; // don't know how to populate this from caller service-factory.js to access the correct API
export default {
get() {
return service.get(`${resource}`);
}
}
Is there a way to set the resource variable when service-factory.js calls the generic-service.js file? Or perhaps there is a way to incorporate the logic from generic-service.js directly into service-factory.js...? Or maybe there is a simpler approach than these options...?
OUTCOME
Many thanks to #Estus Flask for the answer and follow up information. For now, I decided to put all the services logic into src/services/index.js. If the project grows beyond needing more than createService I'll deal with refactoring at that point. Below is the code that seems to be working -
src/services/index.js -
import axios from 'axios';
const axiosInstance = axios.create({
baseURL: 'advancedplanningextension/api/'
});
const createService = resource => ({
get() {
return axiosInstance.get(`${resource}`);
}
// add more here
});
const resources = {
officialScenarios: 'scenariolog',
parameters: 'parameter'
// add more here
}
export const service = {
get: name => createService(resources[name])
}
The values in resources are the specific API endpoints. So when the code calls the service for officialScenarios for example, the URL for that API endpoint will be 'advancedplanningextension/api/scenariolog'.
src/plugins/service.js -
import { service } from '../services/index';
export default {
install: function(Vue) {
Object.defineProperty(Vue.prototype, '$service', { value: service });
}
}
Example usage in src/views/official-scenarios.vue remains the same as shown in the original question.
This can be solved with factory function:
base-service.js
import axios from 'axios';
export const axiosInstance = axios.create({
baseURL: 'advancedplanningextension/api/'
});
export const createService = resource => ({
get() {
return axiosInstance.get(`${resource}`);
},
// etc.
});
foo-service.js
import { createService } from './base-service';
export default createService('foo');
service-factory name is misleading because it isn't really a factory, just a wrapper module. The same thing can be rewritten more efficiently as barrel module (which is commonly index module):
services/index.js
export { default as foo } from './foo-service';
export { default as bar } from './foo-service';
Then it gets all benefits of ES modules such as tree-shaking and can be used as:
import * as services from './services';
services.foo.get(...);

Vuex: Unknown Getter in a Feature-Scoped Module

I'm using Vuex stores in a "feature-scope structure" for the first time and have been having difficulties tracing why I am getting a [vuex] unknown getter: $_kp/kp - (Vue/Vuex isn't throwing much of a bone with this other than just the quoted error).
UPDATE: I turned on store.subscribeAction() to see if that give up any more info. Here is the printed log (I'm not seeing any this useful but hopefully it helps you).
Action Type: $_kp/getKpIndex
Action Payload: undefined
Current State: {ob: Observer} $_kp: Object kp: "2" //<- That is what I'm trying to get - "2"!
UPDATE-2: I'm using Vues Inspector now as well and it shows the following:
| State
| - $_kp: object
| - kp: "3"
| Mutation
| - payload: "3"
| - type: "$_kp/KP_DATA_UPDATED"
Any help with this is greatly appreciated and I hope this can be useful for who sets their stores in this manner.
SomeElement.vue:
<script>
import {mapGetters} from 'vuex';
import store from '../_store';
export default {
name : 'KpIndexElement',
parent: 'AVWX',
computed: {
...mapGetters({
kp: '$_kp/kp', //<-- HERE?
}),
},
created() {
const STORE_KEY = '$_kp';
if (!(STORE_KEY in this.$store._modules.root._children)) {//<= I think there is an issue with this too
this.$store.registerModule(STORE_KEY, store);
}
},
mounted() {
this.$store.dispatch('$_kp/getKpIndex');
},
}
</script>
<template>
<p><strong>Kp: </strong>{{ kp }}</p>
</template>
The Store index.js
import actions from './actions';
import getters from './getters';
import mutations from './mutations';
var state = {
kp: '',
};
export default {
namespaced: true,
state,
actions,
getters,
mutations,
};
actions.js:
import api from '../_api/server';
const getKpIndex = (context) => {
api.fetchKpData
.then((response) => {
console.log('fetch response: ' + response)
context.commit('KP_DATA_UPDATED', response);
})
.catch((error) => {
console.error(error);
})
}
export default {
getKpIndex,
}
mutations.js
const KP_DATA_UPDATED = (state, kp) => {
state.kp = kp;
}
export default {
KP_DATA_UPDATED,
}
...and finally the getters.js
const kp = state => state.kp;
export {
kp,
};
Syntax for mapGetters when using namespaces is as follows :
...mapGetters('namespace', [
'getter1',
'getter2',
... // Other getters
])
In your case :
...mapGetters('$_kp', [
'kp'
])
The first argument is the namespace, the second the payload containing the getters you want to use.
Also, as noted in the comments by #Ijubadr, I'm not sure mapGetters is evaluated after you registered your store module. To work around that, you might have to drop the use of mapGetters and declare your STORE_KEY as a data, then define a computed getter using STORE_KEY in its definition (I renamed it storeKey in the example below since this is no longer a constant):
computed: mapState('$_kp',{
kpIndex: 'kp'
}),
created() {
this.storeKey = '$_kp';
if (!(this.storeKey in this.$store._modules.root._children)) {
this.$store.registerModule(this.storeKey, store);
}
},
mounted() {
this.$store.dispatch('$_kp/getKpIndex');
}

Separating vuex stores for dynamically created components

This was the question got me stuck for a little bit. Unfortunately, I coudn't find answer here (asking also didn't help). So after doing some research and asking here and there, it seems that I got the solution to this issue.
If you have a question that you already know the answer to, and you
would like to document that knowledge in public so that others
(including yourself) can find it later.
Of course, my answer may not be the ideal one, moreover I know it is not, that's the key point why I'm posting - to improve it.
Note, I'm not using actions in example. The idea is the same.
Let's begin with stating the problem:
Imagine we have App.vue which dynamically generates its local component named Hello.
<template>
<div id="app">
<div>
<hello v-for="i in jobs" :key="i" :id="i"></hello>
<button #click="addJob">New</button>
</div>
</div>
</template>
<script>
import Hello from './components/Hello'
export default {
components: {
Hello
}...
store.js
export const store = new Vuex.Store({
state: {
jobs: []
}
})
We are using v-for directive to generate components by iterating through an array jobs. Our store as of now consists of only state with an empty array.
Button New should do 2 things:
1) create new component Hello, in other words add element to jobs (let it be numbers), which are going to be assigned as key and id of <hello>, and passed to local component as props.
2) generate local stores - modules - to keep any data scoped to newly created components.
Hello.vue
<template>
<div>
<input type="number" :value="count">
<button #click="updateCountPlus">+1</button>
</div>
</template>
export default {
props: ['id']
}
Simple component - input with a button adding 1.
Our goal is to design something like this:
For the first operation of NEW button - generating components - we add mutation to our store.js
mutations: {
addJob (state) {
state.jobs.push(state.jobs.length + 1)
...
}
Second, creating local modules. Here we're going to use reusableModule to generated multiple instances of a module. That module we keep in separate file for convinience. Also, note use of function for declaring module state.
const state = () => {
return {
count: 0
}
}
const getters = {
count: (state) => state.count
}
const mutations = {
updateCountPlus (state) {
state.count++
}
}
export default {
state,
getters,
mutations
}
To use reusableModule we import it and apply dynamic module registration.
store.js
import module from './reusableModule'
const {state: stateModule, getters, mutations} = module
export const store = new Vuex.Store({
state: {
jobs: []
},
mutations: {
addJob (state) {
state.jobs.push(state.jobs.length + 1)
store.registerModule(`module${state.jobs.length}`, {
state: stateModule,
getters,
mutations,
namespaced: true // making our module reusable
})
}
}
})
After, we're going to link Hello.vue with its storage. We may need state, getters, mutations, actions from vuex. To access storage we need to create our getters. Same with mutations.
Home.vue
<script>
export default {
props: ['id'],
computed: {
count () {
return this.$store.getters[`module${this.id}/count`]
}
},
methods: {
updateCountPlus () {
this.$store.commit(`module${this.id}/updateCountPlus`)
}
}
}
</script>
Imagine we have lots of getters, mutations and actions. Why not use {mapGetters} or {mapMutations}? When we have several modules and we know the path to module needed, we can do it. Unfortunately, we do not have access to module name.
The code is run when the component's module is executed (when your app
is booting), not when the component is created. So these helpers can
only be used if you know the module name ahead of time.
There is little help here. We can separate our getters and mutations and then import them as an object and keep it clean.
<script>
import computed from '../store/moduleGetters'
import methods from '../store/moduleMutations'
export default {
props: ['id'],
computed,
methods
}
</script>
Returning to App component. We have to commit our mutation and also let's create some getter for App. To show how can we access data located into modules.
store.js
export const store = new Vuex.Store({
state: {
jobs: []
},
getters: {
jobs: state => state.jobs,
sumAll (state, getters) {
let s = 0
for (let i = 1; i <= state.jobs.length; i++) {
s += getters[`module${i}/count`]
}
return s
}
}
...
Finishing code in App component
<script>
import Hello from './components/Hello'
import {mapMutations, mapGetters} from 'vuex'
export default {
components: {
Hello
},
computed: {
...mapGetters([
'jobs',
'sumAll'
])
},
methods: {
...mapMutations([
'addJob'
])
}
}
</script>
Hi and thank you for posting your question and your solution.
I started learning Vuex couple days ago and came across a similar problem. I've checked your solution and came up with mine which doesn't require registering new modules. I find it to be quite an overkill and to be honest I don't understand why you do it. There is always a possibility I've misunderstood the problem.
I've created a copy of your markup with a few differences for clarity and demonstration purposes.
I've got:
JobList.vue - main custom component
Job.vue - job-list child custom component
jobs.js - vuex store module file
JobList.vue (which is responsible for wrapping the job(s) list items)
<template>
<div>
<job v-for="(job, index) in jobs" :data="job" :key="job.id"></job>
<h3>Create New Job</h3>
<form #submit.prevent="addJob">
<input type="text" v-model="newJobName" required>
<button type="submit">Add Job</button>
</form>
</div>
</template>
<script>
import store from '../store/index'
import job from './job';
export default {
components: { job },
data() {
return {
newJobName: ''
};
},
computed: {
jobs() {
return store.state.jobs.jobs;
}
},
methods: {
addJob() {
store.dispatch('newJob', this.newJobName);
}
}
}
</script>
The Job
<template>
<div>
<h5>Id: {{ data.id }}</h5>
<h4>{{ data.name }}</h4>
<p>{{ data.active}}</p>
<button type="button" #click="toggleJobState">Toggle</button>
<hr>
</div>
</template>
<script>
import store from '../store/index'
export default {
props: ['data'],
methods: {
toggleJobState() {
store.dispatch('toggleJobState', this.data.id);
}
}
}
</script>
And finally the jobs.js Vuex module file:
export default {
state: {
jobs: [
{
id: 1,
name: 'light',
active: false
},
{
id: 2,
name: 'medium',
active: false
},
{
id: 3,
name: 'heavy',
active: false
}
]
},
actions: { //methods
newJob(context, jobName) {
context.state.jobs.push({
id: context.getters.newJobId,
name: jobName,
active: false
});
},
toggleJobState(context, id) {
context.state.jobs.forEach((job) => {
if(job.id === id) { job.active = !job.active; }
})
}
},
getters: { //computed properties
newJobId(state) { return state.jobs.length + 1; }
}
}
It's possible to add new jobs to the store and as the "active" property suggest, you can control every single individual job without the need for a new custom vuex module.

Categories