{"componentChunkName":"component---src-templates-page-tsx","path":"/page/5","result":{"pageContext":{"searchData":[{"id":"cjjg9ui0t303s010375m42b9w","title":"Turn a Browser Window into a Notepad with This One-Liner","slug":"/turn-a-browser-window-into-a-notepad-with-this-one-liner","avatar":"https://s3.amazonaws.com/contentkit/static/cjjg9ui0t303s010375m42b9w/OYIaJ1KK_400x400(1).png","date":"July 1, 2020"},{"id":"cjiy7xl9o17w701039gvl18a5","title":"Creating a Collaborative Editor with Draftjs for Fun","slug":"/draft-js-collaborative-editor","avatar":"https://s3.amazonaws.com/contentkit/static/cjiy7xl9o17w701039gvl18a5/draft-js.png","date":"June 28, 2020"},{"id":"cjeqgfsdsnmx90167n7j290kt","title":"How To Include SASS In Your React Project","slug":"/sass-react-webpack","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfsdsnmx90167n7j290kt/parcel-bundler.png","date":"May 1, 2020"},{"id":"cjiy4tseq0tnw0103bc5s7sxj","title":"How to Add a Loading Indicator to Material Ui's Component","slug":"/creating-a-material-ui-button-with-spinner-that-reflects-loading-state","avatar":"https://s3.amazonaws.com/contentkit/static/cjiy4tseq0tnw0103bc5s7sxj/material-ui.png","date":"January 28, 2020"},{"id":"cjkq4u7470ihr0157ad175b12","title":"Connecting to AWS Lambda via WebSockets","slug":"/connecting-to-aws-lambda-via-websockets","avatar":"https://s3.amazonaws.com/contentkit/static/cjkq4u7470ihr0157ad175b12/MQTT.js.png","date":"January 12, 2020"},{"id":"cjeqgfnhknnpe0199zbfwwkd8","title":"6 Really Cool APIs to Have Fun With","slug":"/cool-apis-to-have-fun-with","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfnhknnpe0199zbfwwkd8/scale-api.png","date":"January 1, 2020"},{"id":"cjeqgftvknnr30199dvw266qh","title":"Installing Node Canvas in AWS Lambda","slug":"/node-canvas-aws-lambda","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgftvknnr30199dvw266qh/aws-lambda.jpg","date":"December 31, 2019"},{"id":"cjn5fv8kl0pfj0124wwebufms","title":"GoLang Cheatsheet","slug":"/golang-cheatsheet","avatar":"https://s3.amazonaws.com/contentkit/static/cjn5fv8kl0pfj0124wwebufms/golang.png","date":"October 13, 2019"},{"id":"cjn15y8740liw0175u3s707eh","title":"Scheduled Jobs & Work Queues With Postgresql","slug":"/scheduled-jobs-work-queues-with-postgresql","avatar":"https://s3.amazonaws.com/contentkit/static/cjn15y8740liw0175u3s707eh/kxHkAenZ_400x400.jpg","date":"October 8, 2019"},{"id":"cjmym86fh0jy1013398nfzuqu","title":"Creating a Bot to Refill Parking Meters Using AWS Lambda","slug":"/creating-a-bot-to-refill-parking-meters-using-aws-lambda","avatar":"https://s3.amazonaws.com/contentkit/static/cjmym86fh0jy1013398nfzuqu/prwHMlRn_400x400.jpg","date":"October 7, 2019"},{"id":"cjmsaqt6l09le01046qeukpp9","title":"The USPS Tracking API: How To Track Packages","slug":"/usps-tracking-api","avatar":"https://s3.amazonaws.com/contentkit/static/cjmsaqt6l09le01046qeukpp9/usps.png","date":"October 3, 2019"},{"id":"cjeqgfuyenmy20167rycnmiql","title":"Stormpath vs Firebase - A Side-By-Side Comparison","slug":"/stormpath-vs-firebase-a-side-by-side-comparison","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfuyenmy20167rycnmiql/m3cEA33V_400x400.jpg","date":"September 2, 2019"},{"id":"cjeqgfv88nnrt01997wxd4rnl","title":"Sending emails with Firebase Cloud Functions","slug":"/firebase-functions-sending-emails","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfv88nnrt01997wxd4rnl/firebase.jpg","date":"September 2, 2019"},{"id":"cjeqgfqjknnq20199oe3zwi37","title":"Deploying To DigitalOcean From Travis","slug":"/deploying-to-digitalocean-travis","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfqjknnq20199oe3zwi37/digitalocean.jpeg","date":"August 25, 2019"},{"id":"cjkvpbr6p06z50181fg7xvsc3","title":"Circumventing AWS Lambda's Bundle Size Limit","slug":"/circumventing-aws-lambdas-bundle-size-limit","avatar":"https://s3.amazonaws.com/contentkit/static/cjkvpbr6p06z50181fg7xvsc3/b2lWK7c0_400x400.png","date":"August 15, 2019"},{"id":"cjiy45h7m0iba0111f5imt5em","title":"A Quick Way to List All Unicode Characters (Javascript)","slug":"/unicode-characters-javascript","avatar":"https://s3.amazonaws.com/contentkit/static/cjiy45h7m0iba0111f5imt5em/unicode.png","date":"July 29, 2019"},{"id":"ci1rxc41tm8yi7nd156pz9dom","title":"Kibana Rest API","slug":"/kibana-rest-api","avatar":"https://s3.amazonaws.com/contentkit/static/ci1rxc41tm8yi7nd156pz9dom/elastic.png","date":"July 24, 2019"},{"id":"cjeqgfjgennmw0199wha3j6u0","title":"Airtable As A Database For Middleman [Tutorial]","slug":"/airtable-middleman-database","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfjgennmw0199wha3j6u0/airtable-books.png","date":"June 1, 2019"},{"id":"cjeqgfj5qnnmr0199vzd85xuo","title":"Building A Multiplayer Game With Three.Js + WebSockets","slug":"/multiplayer-game-threejs","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfj5qnnmr0199vzd85xuo/three.jpg","date":"June 1, 2019"},{"id":"cjeqgfi8hnnml0199f1jyo1p5","title":"The Best Sketch Plugins","slug":"/the-best-sketch-plugins","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfi8hnnml0199f1jyo1p5/sketch.png","date":"June 1, 2019"},{"id":"d9920hsu8y7365frf2c6fxrx2","title":"Configuring Vault by Hashicorp in AWS EC2","slug":"/configuring-vault-by-hashicorp-in-aws-ec2","avatar":"https://s3.amazonaws.com/contentkit/static/d9920hsu8y7365frf2c6fxrx2/vault.png","date":"April 15, 2019"},{"id":"cjeqgfvsfnns00199mlbff48i","title":"Best Zapier Alternatives","slug":"/best-zapier-alternatives","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfvsfnns00199mlbff48i/zapier-alternative-workato.png","date":"January 31, 2019"},{"id":"cjn3l18390lyt0158i8qxs5dc","title":"Running A Simple Node Web Server On AWS EC2","slug":"/running-a-simple-node-web-server-on-aws-ec2","avatar":"https://s3.amazonaws.com/contentkit/static/cjn3l18390lyt0158i8qxs5dc/aws.png","date":"October 11, 2018"},{"id":"cjeqgfr4snmwz01673m8l4i51","title":"Graph.Cool vs Firebase","slug":"/graphcool-vs-firebase","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfr4snmwz01673m8l4i51/graphcool.png","date":"October 7, 2018"},{"id":"cjklurup00k0201739mflg9sz","title":"Accessing Redis on an Aws EC2 Instance from the Outside\t","slug":"/accessing-redis-on-an-aws-ec2-instance-from-the-outside","avatar":"https://s3.amazonaws.com/contentkit/static/cjklurup00k0201739mflg9sz/logo.jpeg","date":"August 9, 2018"},{"id":"cjkfzvvxt08up0191l38aadq4","title":"Using PDFtk in AWS Lamba","slug":"/using-pdftk-in-aws-lamba","avatar":"https://s3.amazonaws.com/contentkit/static/cjkfzvvxt08up0191l38aadq4/pdftk.png","date":"August 4, 2018"},{"id":"cjeqgfqsgnnq70199l4vc6vim","title":"Configuring WebSockets on Elastic Beanstalk/EC2","slug":"/websockets-aws-elasticbeanstalk-ec2","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfqsgnnq70199l4vc6vim/aws.png","date":"July 15, 2018"},{"id":"cjit96q2yk0is0111qpbmw66s","title":"Getting Started With Gmail API","slug":"/gmail-api-quickstart","avatar":"https://s3.amazonaws.com/contentkit/static/cjit96q2yk0is0111qpbmw66s/gmail.png","date":"June 28, 2018"},{"id":"cjeqgfo23nmwd0167ynqe7xi8","title":"5 Tips For Using NextJs","slug":"/5-tips-for-using-nextjs","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfo23nmwd0167ynqe7xi8/bHjpwZem_400x400.png","date":"June 1, 2018"},{"id":"cjhxixcyhw4ze01035butoukz","title":"React unstable_deferredUpdates","slug":"/react-unstable_deferredupdates","avatar":"https://s3.amazonaws.com/contentkit/static/cjhxixcyhw4ze01035butoukz/OYIaJ1KK_400x400(1).png","date":"June 1, 2018"},{"id":"cjeqgflpnnnoy01993y7aez8a","title":"Simple Web Scraping With Javascript","slug":"/simple-web-scraping","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgflpnnnoy01993y7aez8a/developer-tools-scraping.png","date":"May 1, 2018"},{"id":"cjeqgfk9lnnn101994ll4ursr","title":"Easy: Add Firebase Facebook Login To Your React App","slug":"/firebase-facebook-login-react","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfk9lnnn101994ll4ursr/firebase.png","date":"May 1, 2018"},{"id":"cjeqgfiqrnmtj0167dmz5g5dg","title":"The 5 Best Static Site Web Hosts","slug":"/the-5-best-static-site-web-hosts","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfiqrnmtj0167dmz5g5dg/digital-ocean.png","date":"May 1, 2018"},{"id":"cjeqgfmi2nmvs01670pgebekn","title":"Tips and Tricks For Using NightmareJs","slug":"/nightmare-js","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfmi2nmvs01670pgebekn/nightmare.png","date":"May 1, 2018"},{"id":"cjeqgfkqennn60199707yp1fb","title":"Why You Should Create Your Next React Web App With Firebase","slug":"/firebase-react-tutorial","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfkqennn60199707yp1fb/firebase.jpg","date":"May 1, 2018"},{"id":"cjeqgfpr6nnpx019978qzmaok","title":"How to Flush Data From Heroku Redis","slug":"/heroku-redis-flushall","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfpr6nnpx019978qzmaok/3wgIDj3j_400x400.png","date":"April 1, 2018"},{"id":"cjeqgfm8xnnp30199vxndhkgh","title":"5 Ways To Style React Components","slug":"/5-ways-to-style-react-components","avatar":null,"date":"April 1, 2018"},{"id":"cjeqgflz7nmvm0167yo7wsjg3","title":"Delete Spreadsheet Rows For Google Sheets","slug":"/delete-rows-google-sheets","avatar":null,"date":"April 1, 2018"},{"id":"cjeqgfp84nnps0199dvru09ll","title":"Make An Uptime Monitoring Microservice In Under 50 Lines of Code","slug":"/twilio-uptime-monitoring-node-tutorial","avatar":null,"date":"April 1, 2018"},{"id":"cjeqgfri4nnqd0199zsv6a9fl","title":"Hexo - The Best Static Site Generator? ","slug":"/deploy-a-hexo-blog","avatar":null,"date":"April 1, 2018"},{"id":"cjeqgfoygnnpn0199f31dgk9m","title":"Airtable As A Minimum Viable Database For Your ReactJs Project","slug":"/airtable-reactjs","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfoygnnpn0199f31dgk9m/airtable-screenshot.png","date":"March 2, 2018"},{"id":"cjeqgfn6pnmw0016793f81hpx","title":"The Advanced Guide To ReactJs Checkboxes","slug":"/reactjs-checkboxes","avatar":null,"date":"January 20, 2018"},{"id":"cjeqgfe8znnly01991jo5xkxx","title":"Minimum Viable GraphQL QuickStart","slug":"/minimum-viable-graphql-quickstart","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfe8znnly01991jo5xkxx/graphcool-schema.png","date":"January 1, 2018"},{"id":"cjeqgfgegnnmb0199jp3hv2xx","title":"How To Add A Contact Form To Your Ghost Blog","slug":"/ghost-blog-contact-form","avatar":null,"date":"November 1, 2017"},{"id":"cjeqgfmvxnnp90199bdti4r8n","title":"PHP Scraping Tutorial - Scrape Reddit With Goutte","slug":"/php-scraping-tutorial-scrape-reddit-with-goutte","avatar":null,"date":"October 7, 2017"},{"id":"cjeqgfpz1nmwp0167c5j46eij","title":"Cannot read property 'loose' of undefined","slug":"/cannot-read-property-loose-of-undefined","avatar":null,"date":"August 25, 2017"},{"id":"cjeqgftlanmxg0167zipvfzp1","title":"Airtable API Example & Tutorial - Generating Charts","slug":"/airtable-api-example-tutorial","avatar":null,"date":"January 31, 2017"},{"id":"cjeqgfphbnmwk0167ldz2mv2q","title":"React onClick Example and Tutorial","slug":"/react-onclick-example-and-tutorial","avatar":null,"date":"January 2, 2017"},{"id":"cjeqgfq8ynmwu0167z6c51ynp","title":"React Tables - How To Render Tables In ReactJS","slug":"/reactjs-tables","avatar":null,"date":"January 2, 2017"},{"id":"cjeqgfuoznmxu0167ayt2zkc3","title":"How To Add A Class in ReactJS","slug":"/reactjs-add-class","avatar":null,"date":"March 14, 2018"},{"id":"cjeqgfu6gnnrb0199nyrddra0","title":"Fetching Github Blame with the GraphQL API V4","slug":"/github-blame-graphql-api-v4","avatar":null,"date":"March 14, 2018"},{"id":"cjeqgfsmsnnqo0199o2x7dl4x","title":"How To Add Meta Descriptions to Middleman Pages","slug":"/middleman-meta-descriptions","avatar":null,"date":"March 14, 2018"},{"id":"cjeqgfs2wnnqi0199rco2vvz2","title":"Zapier Webhook Post Example & Tutorial","slug":"/zapier-webhook-post-example-tutorial","avatar":null,"date":"March 14, 2018"},{"id":"cjeqgfvhunmy90167t6ouqfmb","title":"Setting Up A Job Queue For A Node App [Tutorial]","slug":"/job-queue-node","avatar":null,"date":"March 14, 2018"},{"id":"cjeqgftbcnnqy0199nc1f92te","title":"NextJs vs Create-React-App - A Side-By-Side Comparison","slug":"/nextjs-vs-create-react-app","avatar":null,"date":"March 14, 2018"},{"id":"cjeqgft2pnnqt01990ty8fmeu","title":"How To Create A Modal In ReactJS [Tutorial]","slug":"/reactjs-modal","avatar":null,"date":"March 14, 2018"},{"id":"cjeqgfugpnnrj0199x7anb33t","title":"How To Use Twilio With ReactJS","slug":"/reactjs-twilio-example-tutorial","avatar":null,"date":"March 14, 2018"}],"page":5,"offset":25,"total":57,"nodes":[{"id":"cjkfzvvxt08up0191l38aadq4","title":"Using PDFtk in AWS Lamba","slug":"using-pdftk-in-aws-lamba","published_at":"2018-08-04T22:52:56.509","created_at":"2018-08-04T22:33:51","excerpt":"","image":{"id":"cjkg0kx6k0bq101916abkxoiu","url":"static/cjkfzvvxt08up0191l38aadq4/pdftk.png"},"posts_tags":[{"tag":{"id":"3pl7ejawubufz70vjnad","name":"AWS"}}],"date":"August 4, 2018","html":"\n
If you're manipulating PDFs using AWS Lambda you'll probably need PDFtk at some point.
\nPDFtk is the Swiss army knife of PDF manipulation. PDFtk is preinstalled on Mac.
\nSo locally on my computer I can get the number of pages in a PDF with this PDFtk command.
\nBut obviously this won't work in AWS Lambda because PDFtk is not a preinstalled binary.
\nThe work around is:
\nnpm i pdftk-lambda
\nAnd then require the package at the top of your Lambda function:
\n// handler.js\nrequire('pdftk-lambda')\nmodule.exports.handler = (event, context) => {\n // ...\n}\n
\nThis package includes PDFtk prebuilt on Amazon Linux. You must require it at the top of your function to update process.env.PKG_CONFIG_PATH and process.env.LD_LIBRARY_PATH to point to the binary in node_modules/pdfktk-lambda/bin.
","avatar":"https://s3.amazonaws.com/contentkit/static/cjkfzvvxt08up0191l38aadq4/pdftk.png"},{"id":"cjeqgfqsgnnq70199l4vc6vim","title":"Configuring WebSockets on Elastic Beanstalk/EC2","slug":"websockets-aws-elasticbeanstalk-ec2","published_at":"2018-07-15T09:50:37.548","created_at":"2018-03-14T02:16:39","excerpt":"Here's a quick-and-dirty guide to configuring WebSockets on an AWS EC2 instance/Elastic Beanstalk.","image":{"id":"an5lsj9zl9202rhdox9mvgagr","url":"static/cjeqgfqsgnnq70199l4vc6vim/aws.png"},"posts_tags":[],"date":"July 15, 2018","html":"Here's a quick-and-dirty guide to configuring WebSockets on an AWS EC2 instance/Elastic Beanstalk.
\nWithout any configuration on EC2, the client side will be unable to connect to a WebSocket on the server.
\nWebSockets allow two-way communication between client and server.
\nThe WebSocket protocol consists of an opening handshake followed by bidirectional messages layered over TCP. The handshake is the bridge from HTTP to WS. The client sends a run-of-the-mill get request with headers that look like these:
\nGET /chat HTTP/1.1\nHost: example.com:8000\nUpgrade: websocket\nConnection: Upgrade\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\nSec-WebSocket-Version: 13
\nIf the negotiation is successful the WS server responds with something like:
\nHTTP/1.1 101 Switching Protocols\nUpgrade: websocket\nConnection: Upgrade\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
\nFor WS to work on elastic beanstalk/EC2, the server must listen for incoming socket connections using a standard TCP socket.
\nThe first step to running a WebSocket server on EC2/Elastic Beanstalk is setting the protocol to TCP (rather than HTTP) and listening on PORT 80.
\n\nNext, navigate to EC2 > Network & Security > Security Groups.
\nSelect the security group that's associated with your EC2 instance, click the Inbound tab, and click edit.
\nYou'll need to add a rule that looks something like the following:
\n\nThe type should be HTTP, the protocol TCP, the PORT 80, and the source Anywhere (or 0.0.0.0/0, ::/0).
\nWhen a WS client issues a handshake request, the WS server needs to respond with the right headers. For example, the server needs to respond with:
\nUpgrade: websocket\nConnection: Upgrade
\nWith Elastic Beanstalk, EC2 instances are transient. Therefore imparting the right NGINX config to one particular box is insufficient. Instead, we can write a recipe with pre- and post-deploy hooks. Creating a hook is as simple as adding a file in your_project_root/.ebextensions/my_config_file.config.
\nIn the example below, we tell Elastic Beanstalk to create two new files. The first lives at/etc/nginx/conf.d/01_websockets.conf on the EC2 instance. The contents are an NGINX configuration file that sets the Upgrade and Connection headers.
\nThe second file is bash script that renames the default Elastic Beanstalk NGINX proxy configuration file so that we use the new one.
\n\n
","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfqsgnnq70199l4vc6vim/aws.png"},{"id":"cjit96q2yk0is0111qpbmw66s","title":"Getting Started With Gmail API","slug":"gmail-api-quickstart","published_at":"2018-06-28T02:39:14.068","created_at":"2018-06-24T19:55:49","excerpt":"","image":{"id":"e81sym07cyoy7cupc5kxcwajs","url":"static/cjit96q2yk0is0111qpbmw66s/gmail.png"},"posts_tags":[],"date":"June 28, 2018","html":"Gmail's API is robust and useful. If you're building an application that interfaces with Gmail you might find these code snippets useful.
\nTo access Gmail's API, you'll need to obtain credentials. This walk-through might come in handy with that.
\nOnce you have credentials, the process is roughly as follows:
\nnpm i googleapis --save
const auth = new require('googleapis').auth.OAuth2(client_id, client_secret, callback_urls[0]).
auth.generateAuthUrl
. Redirect the user to the aforementioned url. The user will land on a google-hosted page that prompts them to login with their Gmail account. The login page also alerts the user to the privileges that the app is requesting (see scopes
below).http://localhost:3000/callback
as the callback url.http://localhost:3000/callback?code=xxxx
. This code is needed to request an access token with auth.getToken.
const http = require('http')\nconst url = require('url')\nconst fetch = require('node-fetch')\nconst google = require('googleapis')\nconst OAuth2 = google.auth.OAuth2\nconst gmail = google.gmail({ version: 'v1' })\nconst credentials = require('./credentials.json')\nconst auth = new OAuth2(\n credentials.client_id,\n credentials.client_secret,\n credentials.redirect_uris[0]\n)\nconst scopes = [\n 'https://mail.google.com/',\n 'https://www.googleapis.com/auth/gmail.compose',\n 'https://www.googleapis.com/auth/gmail.modify',\n 'https://www.googleapis.com/auth/gmail.send'\n]\n// get access tokens from the \"code\" query parameter provided by Google\nconst getToken = (auth, code) => new Promise((resolve, reject) => {\n auth.getToken(code, (err, tokens) => {\n resolve(tokens)\n })\n}\nconst createFetch = (token, { endpoint }) => {\n return fetch('https://www.googleapis.com' + endpoint, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${token}`,\n 'User-Agent': 'google-api-nodejs-client/0.10.0',\n host: 'www.googleapis.com',\n accept: 'application/json'\n }\n }).then(resp => resp.json())\n}\nconst handler = async (req, res, next) => {\n if (req.url.startsWith('/callback')) {\n // 2. After the user grants privileges to your application,\n // Google will redirect them to an endpoint you specify. \n // Next, use the code query parameter to request an access_token. \n // This token can be be reused for up to a day. \n const { code } = url.parse(req.url, true)\n const tokens = await getToken(auth, code)\n \n // 3. Now that we have tokens, we can fetch profile information\n // associated with the Gmail account.\n const endpoint = '/gmail/v1/users/me/profile'\n const profile = await createFetch(tokens.access_token, { endpoint }) \n } else {\n // 1. Redirect user to url provided by generateAuthUrl\n const url = await auth.generateAuthUrl({\n access_type: 'offline',\n scope: scopes\n })\n res.writeHead(302, { 'Location': url })\n }\n}\nconst server = http.createServer(handler)\nserver.listen(process.env.PORT || 3000)\n
\nSo far we haven't done anything fancy. Now that we have an access_token we can fetch a list of emails:
\n// pseudocode below\nlet BASE_URI = 'https://www.googleapis.com'\nlet messagesEndpoint = '/gmail/v1/users/me/messages'\nfetch(BASE_URI + messagesEndpoint, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${token}`,\n 'User-Agent': 'google-api-nodejs-client/0.10.0',\n host: 'www.googleapis.com',\n accept: 'application/json'\n }\n }).then(resp => resp.json())\n .then(resp => {\n console.log(resp)\n // { messages: ['1641febef6e6b8a1', '16420ce2f95226e5'] }\n })
\nAs you might expect, the request to /gmail/v1/users/me/messages returns a list of message IDs corresponding to emails.
\nWe can then fetch the actual content of emails and metadata as follows:
\nlet BASE_URI = 'https://www.googleapis.com'\nlet messageEndpoint = `/gmail/v1/users/me/messages/${id}?format=full` // id comes from the previous code snippet\nfetch(BASE_URI + messageEndpoint, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${token}`,\n 'User-Agent': 'google-api-nodejs-client/0.10.0',\n host: 'www.googleapis.com',\n accept: 'application/json'\n }\n }).then(resp => resp.json())\n .then(resp => {\n console.log(resp)\n // {\n // \"id\": \"...\", \n // \"threadId\": \"...\",\n // \"snippet\": \"\", \n // \"headers\": [...],\n // \"payload\": {\n // \"parts\": [{\n // \"partId\": \"0\",\n // \"mimeType\": \"text/plain\",\n // \"filename\": \"\",\n // \"headers\": [\n // {\n // \"name\": \"Content-Type\",\n // \"value\": \"text/plain; charset=utf-8\"\n // },\n // {\n // \"name\": \"Content-Transfer-Encoding\",\n // \"value\": \"quoted-printable\"\n // }\n // ],\n // \"body\": {\n // \"size\": 747,\n // \"data\": \"SGV5IHdoYXQncyBjb29raW4nIGdvb2QgbG9va2luJz8=\"\n // }\n // }]\n // } \n // }\n })\n
\nNote that the response shown in the above snippet is abbreviated. The Gmail API also includes other data like the timestamp and such.
\nThe aspect of the response that requires transformation is payload.parts[n].body.data. The body is base64-encoded binary data.
\nIf we make the same request as the code snippet above but instead specify the format \"raw\" you'll receive a response that looks vaguely like the following. The format is specified as a query parameter, e.g., https://www.googleapis.com/gmail/v1/users/me/messages/${id}?format=full.
\n{ \n id: '16441d1cdc8c73f9',\n threadId: '16441d1cdc8c73f9',\n labelIds: [ 'UNREAD', 'CATEGORY_PERSONAL', 'INBOX' ],\n snippet: 'Hey what\\'s up?'',\n historyId: '9714321',\n internalDate: '1530112624000',\n sizeEstimate: 13035,\n raw: 'SGV5IHdoYXQncyBjb29raW4nIGdvb2QgbG9va2luJz8='\n }\n
\nIf you're after the actual content of emails, it is easier to work with the raw format rather than full, because the full format segments the payload.
\nOn the client side, we can decode the raw body with atob:
\nlet text = window.atob('SGV5IHdoYXQncyBjb29raW4nIGdvb2QgbG9va2luJz8=')\nconsole.log(text)\n// what's cookin' good lookin'\n
\nOn the server, you'll need to use a third-party package that duplicates this functionality like the aptly named btoa.
\nlet text = require('bota')('SGV5IHdoYXQncyBjb29raW4nIGdvb2QgbG9va2luJz8=')
\n","avatar":"https://s3.amazonaws.com/contentkit/static/cjit96q2yk0is0111qpbmw66s/gmail.png"},{"id":"cjeqgfo23nmwd0167ynqe7xi8","title":"5 Tips For Using NextJs","slug":"5-tips-for-using-nextjs","published_at":"2018-06-01T20:43:01.388","created_at":"2018-03-14T02:16:35","excerpt":null,"image":{"id":"jnkgsb08yukpd33os41hwykyg","url":"static/cjeqgfo23nmwd0167ynqe7xi8/bHjpwZem_400x400.png"},"posts_tags":[],"date":"June 1, 2018","html":"\n
NextJs is a framework on top of React. Some features of NextJs include:
\nIt can take some time to get hang of the \"Next\" way of doing things. Here are some things that I wish I knew when I first got started with Next.
\n<Link>
CorrectlyOne of the merits of NextJs is code-splitting and prefetching. Code-splitting means that if the user visits one page they don't need to download the entire app bundle. Prefetching let's the user navigate around your app without inducing a page refresh which makes the app seem less snappy. On a traditional website you click a link, navigate to a new page, and the page refreshes.
\nWith Next you can pass the prefetch prop to . When the user clicks the URI may change in the address bar but the page doesn't reload.
\nWhile Next's prefetch feature is great it can be confusing to use at first.
\nLet's say you have a post at /posts/my-post-slug and you want to link to it. The key is to realize that NextJs prefers query strings to pretty URIs. Next will always prefer /posts/id?=post-slug. In the Link as prop you specify the pretty URI, but then you also need to tell the Link component the query string version with href.
\nimport Link from 'next/Link'\nconst Btn = ({post}) => ( \n <div>\n <Link\n prefetch\n href={{ pathname: '/posts', query: { id: post.slug } }}\n as={`/${post.slug}/`}\n >\n Next\n </Link>\n </div>\n)\nexport default Btn
\nserver.js:
\n// server.js\n...\nserver.get('posts/:id, (req, res) => {\n // Next defaults to using query strings, e.g., /posts?id=slug instead of posts/slug\n // Therefore we merge req.params and req.query and make it accessible to pages/posts.js.\n const query = Object.assign({}, req.params, req.query)\n return app.render(req, res, '/posts', query)\n})
\npages/posts.js
\npages/posts.js\n...\nclass Posts extends React.Component {\n // getInitialProps() runs on both the server or the client depending on the situation.\n static async getInitialProps(ctx) {\n const {req, query} = ctx\n return query\n }\n render() {\n return(\n <div></div>\n )\n }\n}
\nLink
To Serve Global StylesheetsSometimes you don't feel like using the latest and greatest way to style React components. You just want to use a static, global stylesheet. Maybe SASS. To accomplish this, see the following code snippet below. We use Next's Head component.
\nIn production, we include a standard link to the stylesheet. But in development we use dangerouslySetInnerHTML to inline the css.
\nThe following snippet is a
// components/Layout.js\nimport Head from 'next/head'\nimport inlineCSS from '../css/main.scss'\nclass Layout extends React.Component {\n render() {\n let stylesheet\n if (process.env.NODE_ENV === 'production') {\n stylesheet = <link rel=\"stylesheet\" type=\"text/css\" href=\"/assets/main.css\" />\n } else {\n stylesheet = <style dangerouslySetInnerHTML={{ __html: inlineCSS }} />\n }\n return(\n <div>\n <Head>\n <title></title>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n {stylesheet}\n </Head>\n {this.props.children}\n </div>\n )\n }\n}
\nThen on the server (assuming you're using Express):
\n// server.js\nconst sass = require('node-sass')\nconst path = require('path')\nconst fs = require('fs')\nconst sassResult = sass.renderSync({\n file: path.resolve(__dirname, '../../source/css/main.scss'),\n outputStyle: 'compressed',\n})\n server.get('/assets/main.css', (req, res) => {\n res.setHeader('Content-Type', 'text/css')\n res.setHeader('Cache-Control', 'public, max-age=2592000')\n res.setHeader('Expires', new Date(Date.now() + 2592000000).toUTCString())\n res.send(sassResult.css)\n })
\nIn next.config.js:
\n// next.config.js\nmodule.exports = {\n webpack: (config, { dev }) => {\n config.module.rules.push(\n {\n test: /\\.(css|scss)/,\n loader: 'emit-file-loader',\n options: {\n name: 'dist/[path][name].[ext]',\n },\n },\n {\n test: /\\.css$/,\n loader: 'babel-loader!raw-loader',\n },\n {\n test: /\\.scss$/,\n loader: 'babel-loader!raw-loader!sass-loader',\n },\n )\n if (config.resolve.alias) {\n delete config.resolve.alias.react\n delete config.resolve.alias['react-dom']\n }\n return config\n },\n}
","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfo23nmwd0167ynqe7xi8/bHjpwZem_400x400.png"},{"id":"cjhxixcyhw4ze01035butoukz","title":"React unstable_deferredUpdates","slug":"react-unstable_deferredupdates","published_at":"2018-06-01T20:40:16.066","created_at":"2018-06-02T14:59:50","excerpt":"On React's New unstable_deferredUpdates","image":{"id":"wkhynd6w9tugaawmlr6pvnuq0","url":"static/cjhxixcyhw4ze01035butoukz/OYIaJ1KK_400x400(1).png"},"posts_tags":[{"tag":{"id":"1q3fvn94th4blyj7f35g","name":"React"}}],"date":"June 1, 2018","html":"react-dom recently added a new export, unstable_deferredUpdates. The source code can be found in react-reconciler: https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactFiberScheduler.js#L1399
\ndeferredUpdates let's you defer a low-priority setState call until later.
\nCulling unnecessary setState calls is an easy way to improve performance in a react app. Excessive setState calls will flog the render cycle and lead to unnecessary re-renders.
\nIn source code it's most common to see setState called with a simple key/value pair:
\nsetState({ loading: true })
\nsetState also accepts a function. The function is called with the current state.
\nsetState(state => {\n return { loading: true }\n})
\nIf null is returned, then the tree is not re-rendered:
\nsetState(state => {\n if (state.loading) {\n // no work performed\n return null\n }\n return { loading: true }\n})\n
\nFunctional setState is particularly useful in the case of deferredUpdates.
\nBecause the update is deferred until later, this.state may be stale. Actually, this.state may also be stale. But with deferredUpdates this effect is more pronounced. Functional setState guards against a stale state.
\nload = () => {\n deferredUpdates(() => {\n this.setState(currentState => {\n // this.state !== currentState\n // this.state may be stale\n if (currentState.loading) {\n // noop\n return null\n }\n return { loading: true }\n })\n })\n}
\ndeferredUpdates are also cancellable. For example, you may be familiar with the error:
\nCan't call setState (or forceUpdate) on an unmounted component\n
This can be avoided by cancelling a deferred update to state by returning null inside setState.
\nimport React from 'react'\nimport { unstable_deferredUpdates as deferredUpdates } from 'react-dom'\nclass App extends React.Component {\n state = {\n data: undefined,\n loading: true\n }\n isCancelled = false\n fetchDataFromThirdPartyApi = async () => {\n let resp = await fetch('https://api.ipify.org?format=json')\n let data = await resp.json()\n deferredUpdates(() => {\n this.setState(state => {\n if (isCancelled) {\n // noop\n return null\n }\n return { data, loading: false }\n })\n })\n }\n \n componentDidMount () {\n this.fetchDataFromThirdPartyApi() \n }\n \n componentWillUnmount () {\n this.isCancelled = true\n }\n}
\nSuppose you want to make a request to a third-party API, but show a spinner while the request is in-flight.
\nYou might do something like this:
\nload = async () => {\n this.setState({ loading: true })\n let data = await this.fetchDataFromThirdPartyApi()\n this.setState({ loading: false, data: true })\n}\n
\nThis has a few problems. setState is asynchronous, so we'll be requesting data before this.state.loading is updated. We could use the second, callback argument of setState to only start fetching data after state has been updated. However, it may be faster to call setState without awaiting the update to begin fetching data immediately.
\nWith deferredUpdates:
\nload = async () => {\n // promisify setState\n await new Promise((resolve, reject) => {\n this.setState(prevState => {\n if (prevState.loading) {\n reject()\n return null\n }\n return { loading: true }\n }, resolve)\n })\n let data = await this.fetchDataFromThirdPartyApi()\n deferredUpdates(() => {\n this.setState(() => {\n // if the component is unmounted, avoid the update\n if (this._isUnmounted) return null\n return { loading: false, data }\n })\n })\n }\n \n componentWillUnmount () {\n this._isUnmounted = true\n }
\nOne downside of the above snippet is it adds some code complexity and a simple setState({ [key]: value }) is more readable/scannable.
\nIn the future, I hope the React team will expose more react internals to allow users to interact with the update queue.
","avatar":"https://s3.amazonaws.com/contentkit/static/cjhxixcyhw4ze01035butoukz/OYIaJ1KK_400x400(1).png"}]}}}