- 投稿日:2021-01-10T23:47:54+09:00
LINEボットでGoogle Alertを通知
Google Alertは、キーワードを登録しておくと、ウェブ上の新着コンテンツを知らせてくれるサービスです。私は、今まで、メールで受信していたのですが、せっかくコンテンツを通知してくれたのに、時間がたつと埋もれてしまっていました。
そこで、新着コンテンツが来たタイミングでLINEボットで通知すると同時に、データベースに登録して、あとでも参照できるようにしてみます。
2か所でNode.jsを使います。
1つ目は、コンテンツの定期的な取得のため。
2つ目は、過去コンテンツの格納とLINEボット用のサーバです。過去コンテンツは、MySQLサーバに格納しています。
もろもろはGitHubに上げておきました。
poruruba/GoogleAlert
https://github.com/poruruba/GoogleAlert流れ
①Googleアラートに、キーワードを登録しておきます。そうすると、RSSフィードのURLが取得できます。
②Node.jsなどで、定期的にRSSフィードからコンテンツを取得するとともに、Node.jsサーバにコンテンツ登録を依頼します。
③Node.jsサーバでは、コンテンツがすでにデータベースに登録されているか確認し、登録されていない場合はデータベースに登録します。それと同時に、LINEボットにコンテンツをメッセージとして送信します。
④ユーザは、LINEアプリにコンテンツがメッセージで送信されてきます。
⑤(必要に応じて)ユーザはLIFFアプリを起動し、Node.jsサーバからコンテンツ一覧を取得し表示します。準備:Googleアラートにキーワードを登録
以下のサイトでキーワードを登録します。
Googleアラート
https://www.google.co.jp/alertsアラートを作成、と表示されているところにキーワードを入力します。
今回は、「ESP32」としてみました。オプションを表示となっている場合はクリックしてオプションを表示します。ここで、配信先として、自身のGmailアドレスではなく、「RSSフィード」を選択します。
最後に、アラートを作成 を押下します。そうすると、ESP32が追加され、無線のようなマークがでていますので、クリックします。
そうすると、RSSフィードが表示されました。
まだコンテンツの監視が始まったばかりで、コンテンツは1件もないです。ブラウザに表示されているこのURLを覚えておきます。データベースの準備
以下のようなスキーマのテーブルを作成しました。
データベース名:googlealert
テーブル名:items
コンテンツを格納します。テーブル名:members
LINEボットからコンテンツをメッセージ送信する先のユーザIDを格納します。LINEボットの作成
すみませんが、以下の投稿を参考にしてください。
LINEボット名は「Googleアラート」にしてみました。
定期的なコンテンツの取得
定期的なコンテンツ取得は、GoogleアラートのRSSフィードを参照することで行います。
また、これから立ち上げるNode.jsサーバへコンテンツをHTTP Postしています。RSSフィードの参照およびHTTP Postには以下のnpmモジュールを使っています。
rbren/rss-parser
https://github.com/rbren/rss-parsernode-fetch/node-fetch
https://github.com/node-fetch/node-fetchcron_googlealert/index1.js'use strict'; const GOOGLE_ALERT_RSS_URL = process.env.GOOGLE_ALERT_RSS_URL || '【GoogleアラートのRSSフィードのURL】'; const GOOGLE_ALERT_SEARCH_KEYWORD = process.env.GOOGLE_ALERT_SEARCH_KEYWORD || '【Googleアラートに指定したキーワード】'; const base_url = "【Node.jsサーバのURL】"; const fetch = require('node-fetch'); const { URL, URLSearchParams } = require('url'); const Headers = fetch.Headers; const Parser = require('rss-parser'); const parser = new Parser(); (async () =>{ var feed = await parser.parseURL(GOOGLE_ALERT_RSS_URL); if( feed.items.length <= 0 ) return; feed.items.forEach(item =>{ console.log(item.title); }); try{ var created_at = new Date().getTime(); for( var i = 0 ; i < feed.items.length ; i++ ){ var item = feed.items[i]; console.log(item); var param = { keyword: GOOGLE_ALERT_SEARCH_KEYWORD, title: item.title, pubDate: item.pubDate, contentSnippet: item.contentSnippet, id: item.id, link: item.link, created_at: created_at }; await do_post(base_url + '/linebot-googlealert-push', param ); } }catch(error){ console.error(error); } })(); function do_post(url, body) { const headers = new Headers({ "Content-Type": "application/json; charset=utf-8" }); return fetch(new URL(url).toString(), { method: 'POST', body: JSON.stringify(body), headers: headers }) .then((response) => { if (!response.ok) throw 'status is not 200'; return response.json(); }); }以下の部分を、各自の環境に合わせて変更します。これから立ち上げるNode.jsサーバのURLです。
【Node.jsサーバのURL】
Node.jsサーバの実装
とりあえず、以下ダウンロードしてNode.jsサーバを立ち上げます。
unzip GoogleAlert-master.zip cd GoogleAlert-master mkdir cert npm installHTTPSである必要がありまして、SSL証明書をcertフォルダに置きます。フォルダ名は、app.jsを見ればわかります。
起動は以下の通りです。$ node app.jsRSSフィードされたコンテンツを受信する部分を抜粋します。
server/api/controllers/linebot-googlealert/index.jsexports.handler = async (event, context, callback) => { if( event.path == '/linebot-googlealert-push' ){ var body = JSON.parse(event.body); var sql_query = `SELECT id FROM items WHERE id = '${body.id}'`; const [rows] = await dbconn.query(sql_query); var index = rows.findIndex(rows_item => body.id == rows_item.id ); if( index < 0 ){ var sql_insert = `INSERT INTO items (id, keyword, content, pubDate, created_at) VALUES ('${body.id}', '${body.keyword}', '${JSON.stringify(body)}', '${new Date(body.pubDate).getTime()}', ${body.created_at})`; await dbconn.query(sql_insert); var sql_select = `SELECT memberId FROM members`; const [rows] = await dbconn.query(sql_select); var message = app.createSimpleCard(body.title, 'キーワード: ' + body.keyword, body.contentSnippet, 'ブラウザで開く', { type: 'uri', uri: body.link } ); rows.forEach( row =>{ app.client.pushMessage(row.memberId, message); }); } return new Response({}); }else以下の部分を環境に合わせて変更します。
server/api/controllers/linebot-googlealert/index.jsconst DB_HOST = '【MySQLサーバのホスト名】'; const DB_USER = '【MySQLサーバのユーザ名】'; const DB_PASSWORD = "【MySQLサーバのパスワード】"; const DB_PORT = 3306; const DB_DATABASE = "googlealert";上記のうち、以下の部分がLINEボットとしてメッセージ送信する部分です。
server/api/controllers/linebot-googlealert/index.jsvar message = app.createSimpleCard(body.title, 'キーワード: ' + body.keyword, body.contentSnippet, 'ブラウザで開く', { type: 'uri', uri: body.link } ); rows.forEach( row =>{ app.client.pushMessage(row.memberId, message); });以下の部分を環境に合わせて変更します。
server/api/controllers/linebot-googlealert/index.jsconst config = { channelAccessToken: '【LINEボットのチャネルアクセストークン(長期)】', channelSecret: '【LINEボットのチャネルシークレット】', };上記のシークレットを変更しないと、LINEボットのWebhook設定で、Webhook URLの検証が成功しないです。
コンテンツ取得とLINE通知を試してみる。
それでは、LINEボットを自身のスマホのLINEアプリから登録しましょう。
登録が完了すると、LINEボットがそれを認識し、LINEユーザのユーザIDをデータベースに登録します。以下の部分です。
server/api/controllers/linebot-googlealert/index.jsapp.follow(async (event, client) =>{ var memberId = (event.source.type == 'user') ? event.source.userId : event.source.groupId; var sql_insert = `INSERT INTO members (memberId, type) VALUES ('${memberId}', '${event.source.type}')`; await dbconn.query(sql_insert); }); app.unfollow(async (event, client) =>{ var memberId = event.source.type == 'user' ? event.source.userId : event.source.groupId; var sql_delete = `DELETE FROM members WHERE memberId = '${memberId}' AND type = '${event.source.type}'`; await dbconn.query(sql_delete); }); exports.fulfillment = app.lambda();そして、定期的なコンテンツ取得として用意した
cron_googlealert/index1.js
を起動します。起動に便利な、シェルスクリプトを用意しました。
cron_googlealert/index1.sh#!/bin/sh export GOOGLE_ALERT_RSS_URL="【GoogleアラートのRSSフィードのURL】" export GOOGLE_ALERT_SEARCH_KEYWORD=" 【Googleアラートに指定したキーワード】" cd /home/XXXX/projects/node/cron_googlealert /home/XXXX/.nvm/versions/node/v12.19.0/bin/node index.js環境に合わせて以下を変更します。後者は、「ESP32」でした。
【Googleアラートに指定したキーワード】
【GoogleアラートのRSSフィードのURL】$cd cron_googlealert $chmod +x index1.sh $ ./index1.sh(まだコンテンツは見つかっていないかもしれません。気長に待ちましょう)
別のキーワードですが以下のようにDBに登録され、LINEにも通知されます。
同時に、LINEアプリにも通知が届いているかと思います。
あとは、これをCronで起動すればよいです。例えば、1時間ごとに。
$crontab -e ★以下を入力★ 15 * * * * /home/XXXX/projects/node/cron_googlealert/index1.shコンテンツ一覧表示するLIFFアプリ
普通のWebページでもよいのですが、せっかくなのでLIFFアプリにして、LINEアプリ内で表示できるようにします。
LIFFアプリの登録には、LINE Developersで作ったMessaging APIのチャネルではなく、LINEログインのチャネルが必要です。
LINE Developers
https://developers.line.biz/console/登録が完了すると、LIFF IDが割り当たります。
Node.jsサーバの以下の部分を書き換えます。
server/api/controllers/linebot-googlealert/index.jsconst LIFF_ID = "【LINEのLIFF-ID】";画面はこんな感じです。
HTMLはこんな感じです。
public/googlealert/index.html<!DOCTYPE html> <html lang="ja"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-Security-Policy" content="default-src * data: gap: https://ssl.gstatic.com 'unsafe-eval' 'unsafe-inline'; style-src * 'unsafe-inline'; media-src *; img-src * data: content: blob:;"> <meta name="format-detection" content="telephone=no"> <meta name="msapplication-tap-highlight" content="no"> <meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width"> <!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> <script src="https://code.jquery.com/jquery-1.12.4.min.js" integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ" crossorigin="anonymous"></script> <!-- Latest compiled and minified CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous"> <!-- Optional theme --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap-theme.min.css" integrity="sha384-6pzBo3FDv/PJ8r2KRkGHifhEocL+1X2rVCTTkUfGk7/0pbek5mMa1upzvWbrUbOZ" crossorigin="anonymous"> <!-- Latest compiled and minified JavaScript --> <script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script> <link rel="stylesheet" href="css/start.css"> <script src="js/methods_bootstrap.js"></script> <script src="js/components_bootstrap.js"></script> <script src="js/vue_utils.js"></script> <script src="dist/js/vconsole.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script src="https://cdn.jsdelivr.net/npm/js-cookie@2/src/js.cookie.min.js"></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css"> <script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js"></script> <title>Google Alert</title> </head> <body> <div id="top" class="container"> <button class="btn btn-default pull-right" v-on:click="list_update">更新</button> <h1>Google Alert</h1> <br> <h2>本日のアイテム</h2> <div class="panel panel-default" v-for="(value, index) in item_list_today"> <div class="panel-heading"><h3>{{value.content.title}}</h3></div> <div class="panel-body"> <span class="pull-left">pubDate: {{new Date(value.pubDate).toLocaleString()}}</span> <span class="pull-right">keyword: {{value.keyword}}</span> <br><br> {{value.content.contentSnippet}} </div> <div class="panel-footer text-right"> <a class="pull-left" v-bind:href="value.content.link">ブラウザで開く</a> いいね数:{{value.likes}} <button class="btn btn-default btn-sm" v-on:click="change_likes(value, true)">↑</button><button class="btn btn-default btn-sm" v-on:click="change_likes(value, false)">↓</button> </div> </div> <hr> <h2>過去のアイテム</h2> <div class="form-inline"> <button class="btn btn-default" v-on:click="list_update_default">今月</button> <select class="form-control" v-model.number="target_year" v-on:change="list_update"> <option v-for="(value, index) in target_year_list" v-bind:value="value">{{value}}年</option> </select> <select class="form-control" v-model.number="target_month" v-on:change="list_update"> <option value="0">通年</option> <option v-for="(value, index) in [1,2,3,4,5,6,7,8,9,10,11,12]" v-bind:value="value">{{value}}月</option> </select> <select class="form-control" v-model.number="has_likes"> <option value="1">いいね有のみ</option> <option value="0">すべて</option> </select> </div> <table class="table table-striped"> <thead> <tr><th>keyworkd</th><th>title</th><th>pubDate</th><th>いいね</th></tr> </thead> <tbody> <tr v-for="(value, index) in item_list" v-if="has_likes==0||value.likes>0"> <td>{{value.keyword}}</td> <td><a v-bind:href="value.content.link">{{value.content.title}}</a></td> <td>{{new Date(value.pubDate).toLocaleString()}}</td> <td>{{value.likes}} <button class="btn btn-default btn-xs" v-on:click="change_likes(value, true)">↑</button><button class="btn btn-default btn-xs" v-on:click="change_likes(value, false)">↓</button> </td> </tr> </tbody> </table> <!-- for progress-dialog --> <progress-dialog v-bind:title="progress_title"></progress-dialog> </div> <script src="js/start.js"></script> </body>Javascriptはこんな感じです。
public/googlealert/js/start.js'use strict'; //var vConsole = new VConsole(); const base_url = "【Node.jsサーバのURL】"; var vue_options = { el: "#top", data: { progress_title: '', // for progress-dialog item_list_today: [], item_list: [], target_month: 0, target_year: 0, target_year_list: [], has_likes: 0 }, computed: { }, methods: { list_update_default: async function(){ this.target_month = this.now.getMonth() + 1; this.target_year = this.now.getFullYear(); return this.list_update(); }, list_update_today: async function(today){ var param = {}; var list = await do_post(base_url + "/linebot-googlealert-list", param ); for( var i = 0 ; i < list.length ; i++ ) list[i].content = JSON.parse(list[i].content); this.item_list_today = list; }, list_update: async function(){ var param = { year: this.target_year, month: this.target_month, }; var list = await do_post(base_url + "/linebot-googlealert-list", param ); for( var i = 0 ; i < list.length ; i++ ) list[i].content = JSON.parse(list[i].content); this.item_list = list; }, change_likes: async function(target, increment){ console.log(target); var target_likes = ( increment ) ? (target.likes + 1) : (target.likes - 1); if( target_likes < 0 ) target_likes = 0; var param = { id: target.id, likes: target_likes }; await do_post(base_url + "/linebot-googlealert-likes", param ); var t1 = this.item_list.find(item => item.id == target.id ); if( t1 ) this.$set(t1, "likes", target_likes); var t2 = this.item_list_today.find(item => item.id == target.id ); if( t2 ) this.$set(t2, "likes", target_likes); }, }, created: function(){ }, mounted: async function(){ proc_load(); this.now = new Date(); for( var i = 0 ; i < 5 ; i++ ) this.target_year_list.push(this.now.getFullYear() - i ); this.target_month = this.now.getMonth() + 1; this.target_year = this.now.getFullYear(); this.list_update_today(); this.list_update(); } }; vue_add_methods(vue_options, methods_bootstrap); vue_add_components(vue_options, components_bootstrap); var vue = new Vue( vue_options ); function do_post(url, body) { const headers = new Headers({ "Content-Type": "application/json; charset=utf-8" }); return fetch(new URL(url).toString(), { method: 'POST', body: JSON.stringify(body), headers: headers }) .then((response) => { if (!response.ok) throw 'status is not 200'; return response.json(); }); }Node.jsサーバ側では、それにこたえられるように、以下のエンドポイントを用意しています。
一覧の取得といいねカウントです。一覧の取得では、本日のコンテンツ、月ごとのコンテンツ、年ごとのコンテンツ、のようにフィルタリングして返しています。server/api/controllers/linebot-googlealert/index.jsif( event.path == '/linebot-googlealert-list' ){ var body = JSON.parse(event.body); var startTime; var endTime; if( !body.year || !body.month ){ var today = new Date(); today.setHours(0, 0, 0, 0); startTime = today.getTime(); var tomorrow = new Date(today); tomorrow.setDate(today.getDate() + 1); endTime = tomorrow.getTime(); }else if( body.year && body.month == 0 ){ var thisYear = new Date(); thisMonth.setFullYear(body.year); thisMonth.setMonth(0); thisMonth.setDate(1); thisMonth.setHours(0, 0, 0, 0); startTime = thisYear.getTime(); var nextYear = new Date(thisYear); nextMonth.setFullYear(thisYear.getFullYear() + 1); endTime = nextYear.getTime(); }else{ var thisMonth = new Date(); thisMonth.setFullYear(body.year); thisMonth.setMonth(body.month - 1); thisMonth.setDate(1); thisMonth.setHours(0, 0, 0, 0); startTime = thisMonth.getTime(); var nextMonth = new Date(thisMonth); nextMonth.setMonth(thisMonth.getMonth() + 1); endTime = nextMonth.getTime(); } var sql_select = `SELECT * FROM items WHERE pubDate >= ${startTime} AND pubDate < ${endTime} ORDER BY pubDate DESC`; const [rows] = await dbconn.query(sql_select); return new Response(rows); }else if( event.path == '/linebot-googlealert-likes' ){ var body = JSON.parse(event.body); var sql_update = `UPDATE items SET likes = ${body.likes} WHERE id = '${body.id}'`; await dbconn.query(sql_update); return new Response({}); }以上
- 投稿日:2021-01-10T22:55:04+09:00
【2021年】React / TypeScript使用のためのDocker + Vagrant環境を構築する
目的
下記の技術を使ったフロントのコンポーネントの作成を行うための環境構築を行いました。
- React
- TypeScript
目的は上記の利用ですが、本記事としてはReact/TypeScriptを触れることはありませんので、Node.jsを利用する環境の構築には利用が可能かと思います。
DockerだけでなくVagrantを採用している理由としては、自分の開発環境がmacであり、
参考記事に記載の理由で動作が遅いことを解消するためです。参考記事
DXを大幅に低下させるDocker for Macを捨ててMac最速のDocker環境を手に入れる
前準備/筆者の環境
参考記事に記載のVagrantやMutagenのインストールが必要です。
筆者の環境としては下記となります。
- MacOS 11.1
- Vagrant 2.2.7
- Mutagen 0.11.8
作成するもの
下記の4つのファイルを作成します。
それぞれのファイルは同一のディレクトリの配置でOKです。
- Vagrantfile
- mutagen.yml
- Dockerfile
- docker-compose.yml
起動をする際にはvagrantから立ち上げて、仮想マシンの中にsshしてdockerの立ち上げとなります。
Vagrantfile
作成する仮想マシンの構成の記載がされているファイルです。
Vagrant.configure('2') do |config| config.vm.box = 'ubuntu/focal64' config.vm.hostname = 'frontcomponent' config.vm.network :private_network, ip: '192.168.60.10' config.vm.network 'forwarded_port', guest: 8000, host: 8000 config.vm.provider :virtualbox do |vb| vb.gui = false vb.cpus = 2 vb.memory = 4096 vb.customize ['modifyvm', :id, '--natdnsproxy1', 'off'] vb.customize ['modifyvm', :id, '--natdnshostresolver1', 'off'] end config.disksize.size = '15GB' config.mutagen.orchestrate = true config.vm.synced_folder './', '/home/vagrant/front_component', type: "rsync", rsync_auto: true, rsync__exclude: ['.git/', 'log/', 'tmp/'] config.vm.provision 'shell', inline: <<-SHELL echo "fs.inotify.max_user_watches = 65536" >> /etc/sysctl.conf && sysctl -p curl -fsSL https://get.docker.com -o get-docker.sh sh get-docker.sh usermod -aG docker vagrant # dockerを更新する場合にはverを調整すること curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose SHELL endportの設定は今後に自分が利用する環境に合わせて設定しています。
あと、vm.provider
などスペックの設定に関しては利用しているPCなどでチューニングすると良いかと思います。他には自分がこれまでにハマったポイントや参考記事を拝見した後に追加した点としては下記となります。利用する場合には注意してください。
hostname
"_"の利用ができないようです。
サービス名などを入れた場合に間違えやすいので注意が必要です。fs.inotify.max_user_watches
node_modulesなど外部のファイルを引いてくるディレクトリが存在する場合にファイル数が多くなりすぎて、エラーとなることがあります。その場合にはファイルのマウントから除外する方が正しいかとは思いますが、typescriptの方定義ファイルなどローカル上にマウントされてないとコード編集時にnode_modules内の型定義ファイルが見つからず加えました。
良い知見をお持ちの方がいたら意見を伺いたいですmutagen.yml
sync: app: mode: "two-way-resolved" alpha: "./" beta: "frontcomponent:/home/vagrant/front_component" ignore: vcs: true paths: - "/log" - "/tmp"vagrantとのファイル同期にmutagenを利用します。
詳細に関しては記事にまとめるほどには理解が追いついていませんので、参考記事を参照していただけると良いかと思います。Dockerfile
Docker Hubに置いてあるDockerのイメージファイルをベースとして、
今回利用するコンテナのベースイメージを作成します。FROM node:15.5.1 ENV LANG C.UTF-8 ENV WORKSPACE=/var/www/front_component/ RUN curl -sL https://deb.nodesource.com/setup_15.x | bash - RUN apt install -y less build-essential nodejs RUN apt install -y vim less RUN npm install n -g RUN n 14.15.4 WORKDIR $WORKSPACEDocker Hubに置いてあるイメージを元に、今回の環境構築用のベースイメージを作成します。
今回は、今後にreact/typescriptを入れていくことを想定しているのでnodeのイメージをして、npmのインストールをしています。docker-compose.yml
version: '3.7' services: front: build: . image: front:1.0 container_name: front tty: true stdin_open: true ports: - "8000:8000" environment: {} volumes: - .:/var/www/front_component # command: "bash -c 'npm i && npm run watch'"コンテナ作成用の設定になります。
コメントアウト部分は今後にreact/typescriptを入れた後に利用する想定ですが、
ここまでの環境構築では利用をしないのでコメントアウトとしています。vagrantおよびdockerの立ち上げ
立ち上げ方
# 先にcdコマンドで上記で作成したファイルの置いてあるディレクトリに移動する vagrant up # vagrantの立ち上げ vagrant ssh # vagrant内へsshでアクセス cd front_component # vagrantにローカルのファイルをマウントしているディレクトリに移動 docker-compose up -d # dockerの立ち上げ以上で環境の立ち上げ完了です。
上記のコマンドと合わせて、自分がよく使うコマンドを記載しておきます。
簡易的な説明のみなので、ちゃんと使う場合には調べて使ってくださいvagrant系
vagrant up # vagrantの立ち上げ vagrant ssh # 立ち上げたvagrantにsshアクセスする vagrant halt # vagrantの終了 vagrant destroy # vagrantで作成した仮装イメージごと削除docker系
docker ps # 起動中のdockerのimageを確認する docker exec -ti <コンテナ名> /bin/bash # コンテナの中にssh的にアクセスする docker attach <コンテナ名> # コンテナで起動中のプロセスを表示する docker logs <コンテナ名> # 強制終了したコンテナの終了時のログなどを見るのに利用する
初投稿ε('∞'*)フゥー
- 投稿日:2021-01-10T20:46:53+09:00
[Angular & Node.js] Udemyで勉強しようWEBアプリケーション開発!
Udemyで勉強しようWEBアプリケーション開発!
というわけでして、Udemyさんの講座で勉強させて頂いております。
対象講座
こちらの講座です : 【Angular11とNode.jsで始める!】JavaScript系 WEBアプリケーション開発コンプリートガイド
成果物
現時点での成果物です : Angular & Node.js WEBアプリケーション
(まだ、最後の「デザインを作りこもう」をやっていないです。)
(2021/01/12追記 : 「デザインを作りこもう」を行いました。)Angularでのフロントエンドの開発と、Node.js、MongoDBによるバックエンドの開発、デプロイまでを学べます。
※ ローカル環境がMacの方向けの教材でして、私はWindows10でやってみているので、ちょっと難しかったです……
ReactとAngularとどっちが良いか?
フロントエンド側については、ReactとAngularとどっちが良いか? ということを思うかもしれませんが、私は両方やってみた方が良いかなと思いました。
Angularの方が、学習の道筋が示されているので、フロントエンド側の開発でのテンプレート的なものを見出しやすいかもしれません。その後、Reactを勉強するとReactを理解しやすいのでは? と感じています。