新着記事

Viewing posts for the category nuxt

新卒エンジニアが今になって就職前にやっておけば良かったと思うこと3選



就社してからは初めてのブログ投稿となります。
お久しぶりです。開発部の鈴木海人です。

株式会社TORICOにエンジニアとして入社して、半年が経ちました。
今回のブログでは、過去の内定をいただいてから入社までの間に対して何もしなかった自分に対して
入社までにやっておいたほうがいいことについてまとめました。
過去の自分のような過ちを他の人が犯さないようにまとめましたのでエンジニア内定をもらって何をすればいいかわからない人はぜひ参考にしてみてください。



私の簡単な経歴はこちら↓
都内私立文系大学卒業
大学3年時の夏に某大手プログラミングスクールに通い、プログラミングの基礎について学ぶ
スクール卒業後、都内のスタートアップの会社で2ヶ月ほどインターン(作業内容は主にLPの作成を行っていました)
大学4年時は就活を行い、株式会社TORICOに内定をいただき、今に至ります。



注意事項
簡単なコーディング知識があることを前提にお話しします。もしプログラミングが全くわからないという人はprogateなどのプログラミングを簡単に学べるサイトでまずは学びましょう。



それでは本題に戻ります。
まず結論からお話しします。以下の3つになります。
  1. タイピング強化

  2. 会社で使用する言語の参考書を1冊読んでおく

  3. ドキュメントで調べる癖をつける





それでは1つずつ説明していきます。

1.タイピング


これはどんな人でも絶対にやっておきましょう。
目安としましてはe-typingで安定してA以上や寿司打で1万円コースクリアでしょうか。
上記のサイトですと日本語入力ですので英単語を打つようなサイトを探してみてもいいかもしれません。
僕は入社してから、過去タイピング練習をしてこなかったことを最も後悔しています。

タイピングを強化しておくメリットには下記が挙げられます。
  1. 仕事スピードが上がる
  2. 成長スピードが上がる
  3. 教えていただいている時の時間を少なくできる


考えてみれば当たり前なのですが、タイピングスピードが2倍になればかけるコードも2倍になり、そのため成長スピードも2倍になります。
逆にタイピングスピードが1/2倍になればかけるコードも1/2倍になり、そのため成長スピードも1/2倍になります。
もはやエンジニアにとって一番重要なのではと思っています。
もちろん最初はコードを書くことよりも調べたり読んだりする時間の方が長いので、タイピングスピードの恩恵をあまり受けられないかもしれません
しかし後々大きく影響してくるので鍛えておきましょう。
後、単純にタイピングで遅くてミスりまくると恥ずかしいです。
1日10分とかでもいいので毎日タイピングの練習をするのがおすすめです。私も練習中です
タイピングに慣れてきたら数字や記号などもしっかりと打ち込めつように練習しましょう。

また少し話はタイピングから話がずれてしまうのですが、
よく使用するショートカットキーの暗記やカーソルの移動スピードmaxなどの使いやすいPC設定も行っておきましょう
こちらもPCを使う上での基礎スキルとなり、使っているか使っていいないかで作業効率が大幅に変わるので意識してみてください。






2.会社で使用する言語の参考書を1冊読んでおく


参考書を読むというのに抵抗感がある人は多いのではないでしょうか?
実際僕もそうでした。ネットなどで調べてみると「ネットに全部載っているのに本を買う必要はない」、「わからないことはその都度ググって調べればいい」など
本に対しては比較的、良い情報が流れていないようなイメージが僕にはあります。
これは私の上司から教えていただいて、確かにとなったのですが、本は体系的(一つ一つのものがある系統に従ってまとまっているさまのことという意味みたい)になっているため正確な情報をしっかりとインプットできるのです。
今までとりあえずわからなくなったらググってを繰り返していたのですが1通り本を読むことにより、もちろん完全暗記はできませんがコードを書いているとき、あれが使えるかなとか、それが出てこなくても
調べて出てきたメソッドなど、そういえばこんなのあったなと思い出せます。
また、おすすめの参考書なのですが
私の上司のおすすめの参考書はとりあえず分厚い本みたいです。。。
残念ながら優しくて短い本では情報量が少なすぎたりであまりお勧めをしていないようです。
参考書を買うときは分厚くて情報量の多い参考書を選びましょう。
こちらもタイピングと同様少ない時間でもいいので移動時間などを活用して少しずつ読み進めましょう。






3.ドキュメントで調べる癖をつける


皆さんはドキュメントで調べ物をしていますか?
僕は基本的にQiitaだったり個人ブログなどを参考にすることが多いです。。。
わかりやすいですよね。。。
なるべく意識はしていますが今でもあまりできていないのが現状です。
ドキュメントで調べる癖をつけた方がいい理由は、正確な情報が手に入れられるからです。
調べ物をしているとき正確な情報じゃないことや、記事が古く参考にならなかったり、バージョン違いで動作しなかったりと
結構クソみたいな記事が上に表示されることはあるあるではないでしょうか
ドキュメントで調べる癖をつけておくと正確な情報を手に入れることができるのはもちろんなのですが
英語で文を読む癖がついたり、英語で調べたりする癖がつくのでためになると思います。
日本語検索とは比べ物にならないほど英語の情報は出てくるので、英語めっちゃできるぜ!って感じを目指さなくてもいいですが
グーグル翻訳を使いながらでも少しずつ調べ物ができるようになると良いです。
プログラミングをしている人にとって英語は切っても切り離せない関係なので
早い段階で慣れておきましょう。





以上3つが私が入社前にやっておけば良かったことになります。
最後にもう1度
  1. タイピング強化

  2. 会社で使用する言語の参考書を1冊読んでおく

  3. ドキュメントで調べる癖をつける



重要順は上から1.2.3となります。
ぜひエンジニアに、これからなる人なりたい人は参考にしてみてください。




サーバレスアプリケーション (HTML/JSのみ) で、Google Analytics API を使ってアクティブユーザー数を表示するダッシュボードを作る

Google Analytics は、API経由で様々な数値を取得することができます。
今回は、API経由でサイトのアクティブユーザー数を取得して、Nuxtで作ったダッシュボード風JSアプリに表示してみます。

Google Analytics の APIから値を表示してウェブページに表示する場合、APIとの通信をサーバサイドで PHP や Python のプログラムで取得してウェブブラウザに表示時する方法と、ウェブブラウザ自体が直接 Google Analytics API にリクエストして表示する方法と、どちらの方法でも実現できます。

サーバサイドで行う場合、認証情報やシステムを隠蔽でき、またHTTPリクエストが無くともバックグラウンドで値を取得し続けることができるなど、大きな利点がありますが、常時起動するサーバを用意しておかないといけないため構築が若干手間です。

今回は、手間をかけずに実現したかったため、ブラウザと静的リソースのみで動作するものを作ります。
APIへの認証は、ブラウザにログインしている Google ユーザーが行います。

認証のしくみ

Googleの提供している APIは、OAuth2 の認証・認可が必要となります。

アプリのページを開くと、Google のログインページを表示して認証を求め、認証されるとダッシュボードアプリの指定したコールバックURL にリダイレクトされるようにします。

リダイレクトされると、コールバックURL末尾の # (ハッシュ, フラグメント) の後にアクセストークンが含まれた状態になりますので、それを nuxt 内の JSでパースし、ブラウザのメモリ(変数)に格納します。

注意点として、認証情報(アクセストークン)がブラウザの変数に格納されるため、XSSが発生した際にアクセストークンが漏洩するリスクがあります。これはJSで認証情報を扱う以上避けられません。

XSSによる認証情報漏洩を防ぐには、GoogleとのAPIリクエストをサーバサイドで行い、サーバとの認証は httpOnly属性のついたセッションクッキーで行うべきですが、今回は、静的リソースなのでXSSは通常発生しないという前提の元、リスクを受容した上でクライアントサイドJSで機能を提供するものとします。

コールバックURLから取得したアクセストークンを認証ヘッダに含めて、ブラウザから Google Analytics にAPIリクエストをすることで、アクティブユーザー数などを取得できます。

アクセストークンは、localStorage に保存すると、セッション終了後も保持されるため漏洩の危険性が上がります。そのため変数として保存するだけにとどめます。ただし、OAuth2の state パラメータは、ブラウザセッションが変更されても維持する必要があるため、localStorage に一時的に保存します。

アプリのページを開くたびに、OAuth2のアクセストークンをリクエストし続けるとすると、毎回 Google のアカウント選択ページが出て面倒なように感じられるかもしれませんが、実際に使ってみるとそんなことはなく、一度ブラウザと Google の認証セッションクッキーが作られればあとはリダイレクトしか発生せず、1秒程度で自動的に認証が終わりアクセストークンが取得済みの状態になりますので、使用感として悪くはありません。

 試しにAPIにリクエストする

https://developers.google.com/analytics/devguides/reporting/realtime/v3/reference/data/realtime/get?#try-it

こちらの Google アナリティクスのページを見ると、APIの説明と共に、中央もしくは右側にAPIのテストリクエストができるフォームが表示されます。

ids に、 ga: を先頭につけた GoogleAnalytics のID番号を書き、metrics は rt:activeUsers を入れ、

下部 「Google OAuth 2.0」と「API key」にチェックを入れ、「Execute」 ボタンを押します。

すると、さらに下部にレスポンスデータが表示されます。
この情報を、nuxt 等ウェブアプリケーションで取得できます。

作り方

Google Cloud Platform のプロジェクトを作る

まず、Google APIを使うため、GCPのプロジェクトを作ります。

GCPのダッシュボードの上部ヘッダから、プロジェクトを新しく作ります。
https://console.cloud.google.com/home/dashboard

ライブラリの追加

プロジェクトを作ったら、APIとサービスページを開きます。
https://console.cloud.google.com/apis/dashboard

左メニューの「ライブラリ」をクリックし、ライブラリの追加ページで、Google Analytics API と Google Analytics Reporting API を有効にします。

認証情報の追加

次は、認証情報ページを開きます。
https://console.cloud.google.com/apis/credentials

上部「認証情報を作成」リンクから、「API キー」を選択し、APIキーを一つ作ります。

続いて、「認証情報を作成」リンクから、「OAuthクライアント ID」を選択します。

アプリケーションの種類 は、今回は「ウェブアプリケーション」で、名前は、適当に「nuxt client」とかにします。

「承認済みの JavaScript 生成元」は、クロスオリジンリクエストを許可するオリジン名です。
平文 http でもOKなので、

http://localhost:3000

などを追加しておきます。

「承認済みのリダイレクト URI」は、OAuth2 の認証フローのリダイレクトURL(コールバックURL)です。
認証完了時、このURLに、アクセストークンが URLに含まれた形でリダイレクトされます。
今回は、

http://localhost:3000/code

としました。
本番用のURLが決まっていたら、その分も追加します。後からでも追加できます。

スクリーンショットの例は、ローカルPCで docker + Nginx でポート80 でサービスする場合を考慮し、
http://localhost も追加してあります。

ちなみに、クライアントID とクライアントシークレット が作成されますが、ブラウザからのリクエストの場合はクライアントシークレットは使いません。

OAuth 同意画面は、今回は社内用途のみ考慮しているので「内部」を選択し、作成します。

プロジェクトの状態によっては、この選択肢は無いかもしれません。

これで、GCP のプロジェクトの設定は終了です。

アプリを書く

今回は、nuxt + TypeScript で書きます。すべてを載せると冗長なので、要点のみ記載します。

流れとしては、

1.
まずブラウザで / ページ表示時。
最初はアクセストークンが無いため、
store/auth.ts の navigateTokenRequestUrl がコールされる。

2.
navigateTokenRequestUrl の中では、ステートパラメータを作ってローカルストレージに保存し、
Google の認証URLに遷移。

3.
認証が完了すると、リダイレクトURL である /code に着地します。

4.
/code では、URLのハッシュ(フラグメント) から、アクセストークンとステートを取得し、
ステートの一致を検証後、アクセストークンを保存し、/ に遷移します。

5.
/ では、今回はアクセストークンがあるため、
コンポーネント内でそのアクセストークンを使って Google API でリアルタイムユーザー数を取得して表示します。

6.
このアクセストークンの寿命は1時間のため、1時間するとGoogle API は http 401 を返すようになります。
その場合、もう一度アクセストークン取得URLに遷移することで、アクセストークンの取り直しをします。

この時、Google とのセッションクッキーが認証済みであれば、ユーザー操作は不要で、一瞬画面がちらつくだけでアクセストークンの更新が完了します。

設定ファイル settings.ts

export const GOOGLE_API = {
authAPIEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
analyticsAPIEndpoint: 'https://www.googleapis.com/analytics/v3/data/realtime',
clientId: '<GoogleAPIの認証情報で作成されてたクライアントID>.apps.googleusercontent.com',
apyKey: 'AIz<GoogleAPIの認証情報で作成されてたAPIキー>o04',
scope: 'https://www.googleapis.com/auth/analytics.readonly'
}

export const GOOGLE_ANALYTICS_ACCOUNTS = [
{
id: '157xxxxx',
title: 'サイト1'
},
{
id: '872xxxxx',
title: 'サイト2'
},
]

型ファイル types/tasks.d.ts

interface TaskResult {
success: boolean;
message: string;
}

Vuex Store モジュール store/auth.ts

import { Action, Module, Mutation, VuexModule } from 'vuex-module-decorators'
import { GOOGLE_API } from '~/settings'

// OAuth2 state パラメータの localStorage一時保存用
const LOCAL_STORAGE_AUTH_STATE_KEY = 'authState'

@Module({
name: 'auth',
stateFactory: true,
namespaced: true
})
export default class extends VuexModule {
accessToken: string = ''

/**
* 認証済みか?
*/
get authorized () {
return this.accessToken !== ''
}

/**
* アクセストークンを保存
*/
@Mutation
setAccessToken (token: string) {
this.accessToken = token
}

/**
* OAuth2 認証URLに移動する
*/
@Action
navigateTokenRequestUrl () {
// 乱数で state パラメータを作成
const authState = [...Array(30)].map(
() => Math.random().toString(36)[2]).join('')
// state パラメータをローカルストレージに保存する
window.localStorage.setItem(LOCAL_STORAGE_AUTH_STATE_KEY, authState)
const params = [
['scope', GOOGLE_API.scope],
['include_granted_scopes', 'true'],
['response_type', 'token'],
['state', authState],
['redirect_uri', `${window.location.origin}/code`],
['client_id', GOOGLE_API.clientId]
]

window.location.href = `${GOOGLE_API.authAPIEndpoint}?` +
params.map(i => `${i[0]}=${encodeURIComponent(i[1])}`).join('&')
}

/**
* OAuth2認証完了後のURLのハッシュ(フラグメント)を解析して、アクセストークンを保存
*/
@Action
parseResponseParamsString (paramsString: string) : TaskResult {
// #key=value&key2=value2 形式の文字列を URLSearchParams にする
const usp = new URLSearchParams(
paramsString.replace(/^#/, '')) as any
// URLSearchParams を辞書型(マップ型)変数に変換
const paramsDict = [...usp.entries()].reduce(
(dict, e) => ({ ...dict, [e[0]]: e[1] }), {})
// localStorage に保存されている state と一致しているか検証
if (paramsDict.state !== window.localStorage.getItem(
LOCAL_STORAGE_AUTH_STATE_KEY)) {
return {
success: false,
message: 'stateが一致していません'
}
}
// state はもう使わないので消す
window.localStorage.removeItem(LOCAL_STORAGE_AUTH_STATE_KEY)
// アクセストークンがあるか検証
if (!paramsDict.access_token) {
return {
success: false,
message: 'アクセストークンが取得できません'
}
}
// アクセストークンがあったので変数に保存する。認証成功。
this.setAccessToken((paramsDict.access_token))
return {
success: true,
message: ''
}
}
}

pages/index.vue

ユーザーが最初に表示するページ

<template>
<div>
<header class="d-flex text-white p-2">
<div class="flex-grow-1 py-1">
Developer Dashboard
</div>
</header>
<div v-if="authorized">
<div class="container-fluid">
<div class="row">
<div
v-for="account in googleAnalyticsAccounts"
:key="account.id"
class="col-6 col-md-4 col-xl-3 my-3"
>
<RealtimePanel
:analytics-id="account.id"
:title="account.title"
/>
</div>
</div>
</div>
</div>
</div>
</template>

<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'
import { authStore } from '~/store'
import RealtimePanel from '~/components/RealtimePanel.vue'
import { GOOGLE_ANALYTICS_ACCOUNTS } from '~/settings'
@Component({
components: {
RealtimePanel
}
})
export default class Index extends Vue {
get accessToken () {
return authStore.accessToken
}

get authorized () {
return authStore.authorized
}

get googleAnalyticsAccounts () {
return GOOGLE_ANALYTICS_ACCOUNTS
}

requestToken () {
authStore.navigateTokenRequestUrl()
}

mounted () {
// 認証済みでなければトークン取得URLへ遷移
if (!authStore.authorized) {
this.requestToken()
}
}
}
</script>

pages/code/index.vue

OAuth API 認証後のリダイレクトURL(コールバックURL)

<!--
OAuth2 認証後にリダイレクトされるURL
URLに含まれるハッシュ(フラグメント)から、アクセストークンを取得して変数に格納する。
-->
<template>
<div>
code received
</div>
</template>

<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'
import { authStore } from '~/store'
@Component({
components: {
}
})
export default class Index extends Vue {
getRequestToken () {
authStore.navigateTokenRequestUrl()
}

async mounted () {
const taskResult = await authStore.parseResponseParamsString(
window.location.hash.replace(/^#/, ''))
if (!taskResult.success) {
throw new Error(taskResult.message)
}
this.$router.push('/')
}
}
</script>

components/RealtimePanel.vue

<!--
GAのアカウント1つに対応
一定時間ごとに、リアルタイムユーザー数を更新し続けるコンポーネント
-->
<template>
<div class="card">
<div class="card-header h2 py-3 text-truncate">
{{ title }}
</div>
<div class="card-body">
<div v-if="errorMessage" class="my-4">
{{ errorMessage }}
</div>
<div v-if="responseSuccess" class="text-center my-4">
<div class="display-1 fw-bold">
{{ activeUsers|addComma }}
</div>
<div class="text-muted small">
Active User
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from 'nuxt-property-decorator'
import { authStore } from '~/store'
import { GOOGLE_API } from '~/settings'

@Component({
components: {
}
})
export default class extends Vue {
activeUsers: number | null = 0
responseSuccess: boolean = false
errorMessage: string = ''

@Prop({
type: String,
required: true
})
title!: string

@Prop({
type: String,
required: true
})
analyticsId!: string

mounted () {
this.reloadPolling()
}

reloadPolling () {
this.reload()
setTimeout(() => {
this.reloadPolling()
}, 20000)
}

async reload () {
const response = await this.$axios.get(
GOOGLE_API.analyticsAPIEndpoint, {
params: {
key: GOOGLE_API.apyKey,
ids: `ga:${this.analyticsId}`,
metrics: 'rt:activeUsers'
},
headers: {
Accept: 'application/json',
Authorization: `Bearer ${authStore.accessToken}`
},
validateStatus: _ => true
}
)
// トークン期限切れ
if (response.status === 401) {
authStore.navigateTokenRequestUrl()
return
}
// 403: アクセス過多など
if (response.status !== 200) {
this.responseSuccess = false
this.errorMessage = `ERROR: ${response.status}`
return
}
if (response.data) {
this.activeUsers = parseInt(response.data.totalsForAllResults['rt:activeUsers'])
this.responseSuccess = true
this.errorMessage = ''
}
}
}
</script>
Search