<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Initial Apps]]></title><description><![CDATA[Incubating software for industry and more]]></description><link>https://www.initialapps.com/</link><image><url>https://www.initialapps.com/favicon.png</url><title>Initial Apps</title><link>https://www.initialapps.com/</link></image><generator>Ghost 5.2</generator><lastBuildDate>Fri, 23 Oct 2026 03:15:38 GMT</lastBuildDate><atom:link href="https://www.initialapps.com/blog/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Introducing the IMC Thailand Purchasing App]]></title><description><![CDATA[The IMC Thailand purchasing app connects product search, sourcing carts, preliminary pricing, and verified quote requests with local supplier and shipping coordination for customers across Asia and worldwide.]]></description><link>https://www.initialapps.com/introducing-the-imc-thailand-purchasing-app/</link><guid isPermaLink="false">6aca50fb6779ab07b238ea64</guid><dc:creator><![CDATA[Andrew Akagawa]]></dc:creator><pubDate>Sat, 10 Oct 2026 14:56:08 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1532079563951-0c8a7dacddb3?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDgyfHx0aGFpbGFuZHxlbnwwfHx8fDE3OTE2NDQwMTN8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1532079563951-0c8a7dacddb3?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDgyfHx0aGFpbGFuZHxlbnwwfHx8fDE3OTE2NDQwMTN8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="Introducing the IMC Thailand Purchasing App"><p>The IMC Thailand purchasing app gives customers a central place to search for products from Thailand, organize sourcing requests, and coordinate the next steps toward purchase and delivery.</p><p>Connected to IMC Intertech&#x2019;s Thailand distribution hub, the app brings customer requirements and local supplier coordination into a shared workflow. It supports customers across Asia and worldwide who need to manage purchases from Thailand, including orders involving several product categories or suppliers.</p><p>The app is available through the <a href="https://imcintertech.co.th/">IMC Thailand website</a>.</p><h2 id="product-search-and-sourcing-requests">Product Search and Sourcing Requests</h2><p>Customers can search for a broad range of eligible products and record what they need within their account. Requests can include product descriptions, reference links, quantities, preferred brands, specifications, and delivery requirements.</p><p>This accommodates both buyers who know the exact item they want and those who need help identifying a suitable option. Restaurant ingredients, packaging, kitchen supplies, retail merchandise, jewelry, and personal-care products are among the categories customers can request.</p><p>Product availability and eligibility are reviewed according to the supplier, item, destination, and shipping requirements.</p><h2 id="preliminary-pricing-and-verified-quotes">Preliminary Pricing and Verified Quotes</h2><p>Preliminary pricing estimates help customers assess a potential purchase before requesting a detailed quotation. Where pricing information is available, these estimates provide an initial reference for comparing options and setting a budget.</p><p>A verified quote follows confirmation of the requested specification, quantity, supplier availability, and applicable charges. Product costs, service fees, and shipping arrangements can then be reviewed before the customer approves the purchase.</p><p>This keeps the initial estimate and the confirmed purchasing decision distinct, particularly when an order includes custom products or goods from several suppliers.</p><h2 id="an-organized-sourcing-cart">An Organized Sourcing Cart</h2><p>The sourcing cart brings individual requests together in one place. Customers can collect items from different searches and review the overall requirement before submitting it for quotation.</p><p>For example, a restaurant purchasing curry paste, serving bowls, and takeaway packaging can organize all three requirements within the same cart. Each item retains its own specification and quantity, while the combined request provides a clearer picture of the order.</p><p>The <a href="https://imcintertech.co.th/thailand-purchasing-agent/">Thailand purchasing guide</a> explains which details help suppliers prepare comparable offers.</p><h2 id="coordination-through-the-thailand-hub">Coordination Through the Thailand Hub</h2><p>Once purchasing requirements are confirmed, the Thailand hub provides a local point of coordination for supplier communication and shipment preparation.</p><p>For suitable orders, goods from multiple suppliers can be brought together before onward shipment. Collection timing, receiving checks, packing, and outstanding items are considered as part of that process.</p><p>The app connects the customer&#x2019;s sourcing request with this local work, helping keep the buying requirements organized as the order moves toward dispatch. The guide to <a href="https://imcintertech.co.th/consolidate-multiple-thailand-suppliers/">combining orders from multiple Thailand suppliers</a> describes the considerations involved.</p><h2 id="supporting-initial-and-repeat-purchases">Supporting Initial and Repeat Purchases</h2><p>The app accommodates a first sourcing request as well as purchases that are expected to recur. Customers can specify whether a requirement is a trial order, a one-time purchase, or part of a regular supply schedule.</p><p>Recording pack sizes, expected usage, and acceptable alternatives helps clarify those requirements. Repeat purchases still require confirmation of current availability, pricing, and delivery conditions.</p><p>For international customers, the IMC Thailand purchasing app provides a common starting point for product discovery, quotation requests, and coordination through the Thailand operation.</p><p>Further information is available at <a href="https://imcintertech.co.th/">IMC Thailand</a>.</p>]]></content:encoded></item><item><title><![CDATA[Is There a Better AI Email App? Introducing Treeoge]]></title><description><![CDATA[Is there a better AI email app? We built Treeoge to make email triage faster, smarter, and less distracting with AI built into the workflow.]]></description><link>https://www.initialapps.com/is-there-a-better-ai-email-app/</link><guid isPermaLink="false">6ac5bb6b6779ab07b238ea40</guid><dc:creator><![CDATA[Andrew Akagawa]]></dc:creator><pubDate>Wed, 07 Oct 2026 03:38:03 GMT</pubDate><media:content url="https://www.initialapps.com/content/images/2026/10/is-there-a-better-ai-email-app.png" medium="image"/><content:encoded><![CDATA[<img src="https://www.initialapps.com/content/images/2026/10/is-there-a-better-ai-email-app.png" alt="Is There a Better AI Email App? Introducing Treeoge"><p><strong>Is there a better AI email app?</strong> That was the question we kept asking ourselves.</p><p>AI has become incredibly good at writing. It can summarize a long email thread, clean up your wording, draft a response, and turn a few scattered thoughts into a polished message.</p><p>However, actually managing email still feels surprisingly manual.</p><p>You open your inbox. You read a message. Then you decide what it means and what needs to happen next.</p><p>Should you reply? Delegate it? Turn it into a task? Save it for later? Mark it done? Delete it?</p><p>Then you move to the next email and do it all over again.</p><p>The AI may be there, but the workflow around it still feels clunky.</p><p>That is why we built <strong><a href="https://treeoge.com/">Treeoge</a></strong>.</p><h2 id="the-problem-with-most-ai-email-apps">The problem with most AI email apps</h2><p>A lot of AI email tools start with the same assumption: the hardest part of email is writing the reply.</p><p>Sometimes that is true.</p><p>Most of the time, though, writing is only one small part of the work.</p><p>Before you respond, you have to understand what the email requires. Then you have to decide what to do with it.</p><p>For example:</p><ul><li>Does this email need a reply?</li><li>Should someone else handle it?</li><li>Is there a task hidden inside it?</li><li>Is the conversation already complete?</li><li>Is this something you can simply ignore or delete?</li></ul><p>As a result, the real problem is not writing email. The real problem is making dozens of tiny decisions about email every day.</p><p>We wanted AI to help with that entire process.</p><h2 id="is-there-a-better-ai-email-app-than-another-sidebar">Is there a better AI email app than another sidebar?</h2><p>That became the bigger question.</p><p><strong>Is there a better AI email app</strong> than one that simply adds a writing assistant, button, or sidebar to the inbox you already use?</p><p>We think there should be.</p><p>AI should not feel like another tool you have to stop and use.</p><p>Instead, it should understand the email you are already reading. It should help you decide what happens next, prepare the work when possible, and then get out of the way.</p><p>The goal is not to spend more time interacting with AI.</p><p>The goal is to spend <strong>less time managing email</strong>.</p><figure class="kg-card kg-video-card"><div class="kg-video-container"><video src="https://www.initialapps.com/content/media/2026/10/drafting.mp4" poster="https://img.spacergif.org/v1/1440x900/0a/spacer.png" width="1440" height="900" playsinline preload="metadata" style="background: transparent url(&apos;https://www.initialapps.com/content/images/2026/10/media-thumbnail-ember90.jpg&apos;) 50% 50% / cover no-repeat;"></video><div class="kg-video-overlay"><button class="kg-video-large-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button></div><div class="kg-video-player-container"><div class="kg-video-player"><button class="kg-video-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button><button class="kg-video-pause-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/><rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/></svg></button><span class="kg-video-current-time">0:00</span><div class="kg-video-time">/<span class="kg-video-duration"></span></div><input type="range" class="kg-video-seek-slider" max="100" value="0"><button class="kg-video-playback-rate">1&#xD7;</button><button class="kg-video-unmute-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/></svg></button><button class="kg-video-mute-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/></svg></button><input type="range" class="kg-video-volume-slider" max="100" value="100"></div></div></div></figure><h2 id="introducing-treeoge">Introducing Treeoge</h2><p>Treeoge is an AI-first email workspace built around one simple idea:</p><p><strong>Triage the inbox first.</strong></p><p>Rather than treating every email as another item in a long chronological list, Treeoge helps you move each message toward an outcome.</p><p>An email may need a reply.</p><p>It may need to be delegated.</p><p>It may create something you need to do later.</p><p>Or it may simply be done or junk.</p><p>Treeoge helps you make those decisions quickly, while AI works alongside you.</p><h2 id="ai-should-be-part-of-the-email-workflow">AI should be part of the email workflow</h2><p>We wanted Treeoge to feel less like an AI tool attached to email and more like email designed around what AI can now do.</p><p>For that reason, Treeoge is focused on the workflow around the message itself.</p><p>It can help you understand what came in, prepare a response, identify follow-up work, and keep the next action clear.</p><p>For replies, the goal is not just to generate generic text.</p><p>Treeoge can help draft the response so you can review it, make any changes you want, and keep moving.</p><p>Likewise, when an email creates work, that work should not stay buried in the inbox. Treeoge is designed to help turn it into something actionable.</p><p>And when a message does not deserve more attention, you should be able to move on quickly.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.initialapps.com/content/images/2026/10/languages.png" class="kg-image" alt="Is There a Better AI Email App? Introducing Treeoge" loading="lazy" width="969" height="649" srcset="https://www.initialapps.com/content/images/size/w600/2026/10/languages.png 600w, https://www.initialapps.com/content/images/2026/10/languages.png 969w" sizes="(min-width: 720px) 720px"><figcaption>A Better AI Email App</figcaption></figure><h2 id="a-better-ai-email-app-should-reduce-decisions">A better AI email app should reduce decisions</h2><p>This is the part we think matters most.</p><p>Email is not just a communication tool anymore.</p><p>It has also become a task manager, reminder system, archive, notification feed, and dumping ground.</p><p>Therefore, opening your inbox often means stepping into a queue of other people&apos;s priorities.</p><p>You may intend to check email for five minutes. Instead, you spend the next hour reacting to whatever arrived.</p><p>A better AI email app should help reverse that relationship.</p><p>You should be able to enter your inbox, understand what came in, decide what matters, and leave with a clear picture of what needs to happen next.</p><p>That is the experience we are building with Treeoge.</p><h2 id="why-initial-apps-built-treeoge">Why Initial Apps built Treeoge</h2><p>At <strong>Initial Apps</strong>, we like building focused products around everyday problems that still feel harder than they should.</p><p>Email is one of those problems.</p><p>The technology around email has changed dramatically. AI models can now understand and generate language at a level that would have seemed impossible only a few years ago.</p><p>Yet the basic inbox workflow has barely changed.</p><p>That disconnect stood out to us.</p><p>We did not want to build another AI writing box.</p><p>Instead, we wanted to rethink the experience of working through email from beginning to end.</p><p>Treeoge is our attempt to do that.</p><h2 id="so-is-there-a-better-ai-email-app">So, is there a better AI email app?</h2><p><strong>Is there a better AI email app?</strong></p><p>We think the answer should be yes.</p><p>A better AI email app should do more than write sentences for you.</p><p>It should help you understand what arrived. It should help you decide what needs to happen. It should help prepare the response. It should surface the work hidden inside the message.</p><p>Most importantly, it should help you keep moving.</p><p>That is what we are building with <strong><a href="https://treeoge.com/">Treeoge</a></strong>.</p><p>If your inbox has started to feel less like communication and more like a daily queue of decisions, Treeoge was built for that problem.</p><p><strong><a href="https://treeoge.com/">Try Treeoge &#x2192;</a></strong></p>]]></content:encoded></item><item><title><![CDATA[Adding App Authentication with PassportJs on a MEVN Stack]]></title><description><![CDATA[<p>This tutorial will teach you how to create signup and login functionality on an app built with Vue3, Express, Mongo, and Node. &#xA0;We&apos;ll start where our last tutorial left off: <a href="https://www.initialapps.com/connecting-our-expressjs-vue3-app-to-a-mongoose-database/">Connecting our ExpressJs/Vue3 App to a Mongoose Database</a>. &#xA0;Following along this example project will show</p>]]></description><link>https://www.initialapps.com/adding-app-authentication-with-passportjs-on-a-mevn-stack/</link><guid isPermaLink="false">64a1f0e8466557050e80de59</guid><dc:creator><![CDATA[Andrew Akagawa]]></dc:creator><pubDate>Sat, 08 Jul 2023 22:06:24 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1578088085518-738839b57548?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDQyfHxrZXlzfGVufDB8fHx8MTY4ODg1MzkxNHww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1578088085518-738839b57548?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDQyfHxrZXlzfGVufDB8fHx8MTY4ODg1MzkxNHww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Adding App Authentication with PassportJs on a MEVN Stack"><p>This tutorial will teach you how to create signup and login functionality on an app built with Vue3, Express, Mongo, and Node. &#xA0;We&apos;ll start where our last tutorial left off: <a href="https://www.initialapps.com/connecting-our-expressjs-vue3-app-to-a-mongoose-database/">Connecting our ExpressJs/Vue3 App to a Mongoose Database</a>. &#xA0;Following along this example project will show you how to get started with user logins.</p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">Git Repo Here:<a href="https://github.com/Initial-Apps/vue3-mevn-authentication"> https://github.com/Initial-Apps/vue3-mevn-authentication</a></div></div><h2 id="prerequisites">Prerequisites</h2><ul><li>This tutorial picks up from our last tutorial: <a href="https://www.initialapps.com/connecting-our-expressjs-vue3-app-to-a-mongoose-database/">Connecting our ExpressJs/Vue3 App to a Mongoose Database</a></li><li>Make sure to have <code>.env</code> variables setup with your MongoDB as this file cannot be in our git repo.</li><li>A basic understanding of <a href="https://www.initialapps.com/building-your-first-app-getting-started-with-git/">git</a>, and npm.</li></ul><h2 id="setup">Setup</h2><p>Let&apos;s start by cloning the project from our <a href="https://www.initialapps.com/connecting-our-expressjs-vue3-app-to-a-mongoose-database/">last tutorial</a>. &#xA0;</p><pre><code>git clone git@github.com:Initial-Apps/vue3-express-mongo.git passportJs-MEVN</code></pre><p>Open a new terminal window, move into the <code>server</code> folder and install our npm modules.</p><pre><code>cd passportJs-MEVN/server
npm install</code></pre><p>Make sure to also have your <code>.env</code> folder setup with your MongoDB key as we did in the <a href="https://www.initialapps.com/building-your-first-app-getting-started-with-git/">last tutorial</a>. &#xA0; </p><p>You can then start the server by running:</p><pre><code>node serverjs</code></pre><p>Next, let&apos;s also setup the client side. Open a new terminal window, move into the <code>client</code> folder and install our npm modules.</p><pre><code>cd client
npm install</code></pre><p>You can then start the client development server by:</p><pre><code>npm run dev</code></pre><h2 id="implementing-passportjs">Implementing PassportJs</h2><p>PassportJs is a popular npm module that runs on our server. &#xA0;This module provides functionality for signing up, as well as logging in and out on our server. &#xA0;Some notable functionalities include validating usernames and passwords, storing logged in user session data for access on our server.</p><p>We start by making a <code>passport</code> directory on our <code>server</code>. &#xA0;Open a new terminal window and make the new directory inside the <code>server</code> folder by running the following from the terminal or using your preferred code editor:</p><pre><code>cd server
mkdir passport</code></pre><p>Then we make a new file titled <code>init.js</code> and add the following:</p><pre><code>const login = require(&apos;./login&apos;);
const signup = require(&apos;./signup&apos;);
const User = require(&apos;../models/user&apos;);

module.exports = function(passport){
	passport.serializeUser(function(user, done) {
		done(null, { 
			_id: user._id,
		});
	});
	passport.deserializeUser(function(userSession, done) {	
		User.findById(userSession._id)
		.then((user)=&gt;{
			console.log(&apos;deserializingUser&apos;);
			done(null, user)
		})
		.catch((err)=&gt;{
			console.log(err);
			done(err)
		})
	});
	login(passport);
	signup(passport);
}</code></pre><p>In summarizing the above, the <code>serialize</code> function stores the user id into the session for our app to reference. &#xA0;The <code>deserialize</code> function can retrieve any user data we specify based on the stored user id. Together, these functions enable Passport.js to maintain user sessions and facilitate authentication throughout the application.</p><p>Then we make a new file titled <code>signup.js</code> and add the following:</p><pre><code>const LocalStrategy = require(&apos;../node_modules/passport-local&apos;).Strategy;
const mongoose = require(&apos;mongoose&apos;);
const User = require(&apos;../models/user&apos;);
const bCrypt = require(&apos;bcrypt-nodejs&apos;);

module.exports = function(passport){
	// LOCAL AUTHORIZATION STRATEGY
	passport.use(&apos;signup&apos;, new LocalStrategy({
		usernameField: &apos;email&apos;,
		passReqToCallback : true // allows us to pass back the entire request to the callback
	},
	function(req, username, password, done) {
		findOrCreateUser = function(){
			// find a user in Mongo with provided username
			User.findOne({ &apos;email&apos; :  username })
			.then((user)=&gt;{
				// If already exists
				if (user) {
					console.log(&apos;User already exists with email: &apos;+username);
					return done(null, false, &apos;That Email Already Exists&apos;);
				} else {
					// Hash the password
					const hash = createHash(password);
					// If there is no user with that email
					// create the user
					let newUser = new User({
						email: username,
						password: hash,
					});	
					// save the user
					newUser.save()
					.then((user)=&gt; {
						console.log(&apos;User Registration succesful&apos;);
						return done(null, newUser);						
					})
					.catch((err)=&gt;{
						if (err){
							console.log(&apos;Error in Saving user: &apos;+err);
							throw err;
						}
					});
				}
			})
			.catch((err)=&gt;{
				console.log(&apos;Error in SignUp: &apos;+err);
				return done(err);
			});
		};
		// Delay the execution of findOrCreateUser and execute the method
		// in the next tick of the event loop
		process.nextTick(findOrCreateUser);
	}));
	// Generates hash using bCrypt
	const createHash = function(password){
		return bCrypt.hashSync(password, bCrypt.genSaltSync(10), null);
	}
}</code></pre><p>The above code first checks for any issues such as searching our database for any existing email address matches to ensure the user email is unique. &#xA0;It then encrypts the user password using <code>bcrypt</code>, &#xA0;stores it to our database on the save function, and returns our new user to our prior created <code>init.js</code> for use in our session. </p><p>Next we create a file called <code>login.js</code> and add the following:</p><pre><code>const LocalStrategy   = require(&apos;passport-local&apos;).Strategy;
const User = require(&apos;../models/user&apos;);
const bCrypt = require(&apos;bcrypt-nodejs&apos;);

module.exports = function(passport){
	passport.use(&apos;login&apos;, new LocalStrategy({
		usernameField: &apos;email&apos;,
		passReqToCallback : true
	},
	function(req, username, password, done) {
		username = username.toLowerCase(); // clear caps
		User.findOne({email: username})
		.catch((err) =&gt; {
			return done(err);
		})
		.then ((user) =&gt; {
			if(!user) {
				console.log(&apos;User Not Found with username &apos;+username);
				return done(null, false, &apos;Invalid Username&apos;);
			}
			// User exists but no password, log the error
			if (user &amp;&amp; !user.password){
				console.log(&apos;No Password&apos;);
				return done(null, false, &apos;No Password&apos;); // redirect back to login page
			}
			// User exists but wrong password, log the error
			if (!isValidPassword(user, password)){
				console.log(&apos;Invalid Password&apos;);
				return done(null, false, &apos;Invalid Password&apos;); // redirect back to login page
			}
			if(isValidPassword(user, password)){
                return done(null, user);
			}
		})
	}));
	const isValidPassword = function(user, password){
		return bCrypt.compareSync(password, user.password);
	}
}</code></pre><p>The above file handles our server logins. &#xA0;When a user logs in, this code validates the email and password by checking that it matches in our database. If all matches up with no errors, it will return our user for session use in our prior created <code>init.js</code> file.</p><h2 id="setting-up-our-database-user-model">Setting Up Our Database User Model</h2><p>Next we will setup our database model. Since we are creating an authentication app, we will need a place to store our user data, such as email addresses and passwords. That&apos;s where our MongoDB comes in. &#xA0;In order to do this, we&apos;ll need to create a data model, outlining what data we will store for each user.</p><p>First, we will create a <code>models</code> folder in our <code>server</code> directory, to hold the data models. &#xA0;If using the terminal, do this by running the following from within our <code>server</code> directory, or use your preferred code editor:</p><pre><code>mkdir models</code></pre><p>Create a new file called <code>user.js</code> and add the following:</p><pre><code>const mongoose = require(&apos;mongoose&apos;);

function toLower (v) {
	if(v){
		return v.toLowerCase();
	}
}

const Users = new mongoose.Schema({
	email: { type: String, set: toLower },
	password: String,
});

module.exports = mongoose.model(&apos;User&apos;, Users);</code></pre><p>A brief explanation of the above: &#xA0;First we require the mongoose module, which is a middleware that connects our app data with our database. &#xA0;The <code>toLower</code> function is used to rewrite all email addresses to lowercase. &#xA0;This is done as a best practice to ensure all email addresses are formatted the same and we don&apos;t accidentally add 2 of the same email addresses because one is capitalized and hence not recognized as different from it&apos;s lowercase counterpart. &#xA0;Finally, we define the data we are collecting in our <code>mongoose.Schema</code>. &#xA0; In our case, we are storing just the user <code>email</code> address, and <code>password</code>. &#xA0;</p><h2 id="building-our-server">Building Our Server</h2><p>Next we setup our server, which runs on Node and Express. &#xA0;Together, these code modules handle all actions performed by our user on the server side. &#xA0;These include actions such as listening for user signups, logins, or logouts. &#xA0;</p><p>Let&apos;s open the <code>server.js</code> file. &#xA0;I am modifying the file from our last tutorial as follows:</p><pre><code>const express = require(&apos;express&apos;)
const mongoose = require(&apos;mongoose&apos;);
const bodyParser = require(&apos;body-parser&apos;);
const session  = require(&apos;express-session&apos;); // Authentication is stored in sessions, so we&apos;ll use express-session
const MongoStore = require(&apos;connect-mongo&apos;);  // Used to connect our session data to our MongoDB
const passport = require(&apos;passport&apos;); // Passport is used for authentication

const app = express()
const port = 3000
require(&apos;dotenv&apos;).config(); 

// Connect to our MONGOOSE DB
const connectString = process.env.MONGODB;  
mongoose.connect(connectString, { useNewUrlParser: true }).then(
  () =&gt; { 
    console.log (&apos;Succeeded connected to database&apos;);
   },
  err =&gt; { 
    console.log (&apos;ERROR connecting to database: &apos; + err);
   }
);

// Initialize Passport
const initPassport = require(&apos;./passport/init&apos;);
initPassport(passport);
// Create the Session
app.use(session({
	secret: process.env.SESSION_SK,
	resave: true,
	saveUninitialized: false,
	cookie: {
    	httpOnly: false,
		sameSite: true,
		maxAge: 10* 4 * 60 * 60 * 1000,
	},
	store: MongoStore.create({
		mongoUrl: connectString,
		ttl: 10*4 * 60 * 60, // logout after 1hr
	})
}));
app.use(passport.initialize()); // initialize passport login sessions
app.use(passport.session()); // for persistent login sessions


app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

app.post(&apos;/api/login&apos;, (req, res, next) =&gt; {
	// general flow, 1) calls passport/login, 2) this function, 3) serializes in passport/init 4) deserializes in passport/init
	passport.authenticate(&apos;login&apos;, function(err, user, info) {
		if (err) {
			console.log(&apos;err&apos;);
			return res.sendStatus(400);
		}
		if (!user) {
			console.log(&apos;no user&apos;);
			return res.sendStatus(400);
		}		
		req.logIn(user, function(err) {
			if (err) { return next(err); } else {
				console.log(req.session)
				// return
				return res.sendStatus(200);					
			}
		});
	})(req, res, next);
});

/*** POST SIGNUP A USER ***/
app.post(&apos;/api/signup&apos;, (req, res, next) =&gt; {
	passport.authenticate(&apos;signup&apos;, registerUser)(req, res, next);
	function registerUser(err, user, info) {
		if (err) { console.log(err); return res.send(err) }
		if (!user) {
			return res.status(400).json(info);
		}
		if (user){
			req.logIn(user, function(err) {
				if (err) { return next(err); }
				// return
				return res.sendStatus(200);
			});
		}
	}
});

/*** LOGOUT A USER ***/
app.post(&apos;/api/logout&apos;, (req, res, next) =&gt; {
	res.clearCookie(&apos;connect.sid&apos;); 
	req.logout(function(err) {
		console.log(&apos;logged out&apos;);
		console.log(err)
		req.session.destroy(function (err) {
			res.send();
		});
	});
});

app.listen(port, () =&gt; {
  console.log(`Example app listening on port ${port}`)
})</code></pre><p>Let&apos;s take a closer look at what was changed. &#xA0;We are requiring 3 new npm modules: <code>express-session</code>, <code>connect-mongo</code>, and <code>passport</code>. &#xA0;</p><pre><code>const session  = require(&apos;express-session&apos;); // Authentication is stored in sessions, so we&apos;ll use express-session
const MongoStore = require(&apos;connect-mongo&apos;);  // Used to connect our session data to our MongoDB
const passport = require(&apos;passport&apos;); // Passport is used for authentication</code></pre><p><code>express-session</code> and <code>connect-mongo</code> are both used to connect our login sessions to our Mongo database. These allow us to store a user login session on our database after the user has logged in. &#xA0;Our server script can then connect and check our database if a user is logged in. &#xA0;</p><p>We then initialize passport <code>initPassport</code> and start a <code>session</code>.</p><pre><code>// Initialize Passport
const initPassport = require(&apos;./passport/init&apos;);
initPassport(passport);
// Create the Session
app.use(session({
	secret: process.env.SESSION_SK,
	resave: true,
	saveUninitialized: false,
	cookie: {
 		httpOnly: false,
		sameSite: true,
		maxAge: 10 * 4 * 60 * 60 * 1000,
	},
	store: MongoStore.create({
		mongoUrl: connectString,
		ttl: 10 *4 * 60 * 60,
	})
}));
app.use(passport.initialize()); // initialize passport login sessions
app.use(passport.session()); // for persistent login sessions</code></pre><p>The above adds our passport <code>init.js</code>, <code>signup.js</code>, and <code>login.js</code> files for use. &#xA0;The session then creates a connection between our server and database to handle the login session. &#xA0;The session will be stored as a cookie on our server, and contain needed login information such as a unique identifier for the session, and when the session should expire for server and database ( <code>maxAge</code> &amp; <code>ttl</code>).</p><p>The last part we added are the API routes:</p><pre><code>app.post(&apos;/api/login&apos;, (req, res, next) =&gt; {
	passport.authenticate(&apos;login&apos;, function(err, user, info) {
		if (err) {
			console.log(&apos;err&apos;);
			return res.sendStatus(400);
		}
		if (!user) {
			console.log(&apos;no user&apos;);
			return res.sendStatus(400);
		}		
		req.logIn(user, function(err) {
			if (err) { return next(err); } else {
				console.log(req.session)
				// return
				return res.sendStatus(200);					
			}
		});
	})(req, res, next);
});

/*** POST SIGNUP A USER ***/
app.post(&apos;/api/signup&apos;, (req, res, next) =&gt; {
	passport.authenticate(&apos;signup&apos;, registerUser)(req, res, next);
	function registerUser(err, user, info) {
		if (err) { console.log(err); return res.send(err) }
		if (!user) {
			return res.status(400).json(info);
		}
		if (user){
			req.logIn(user, function(err) {
				if (err) { return next(err); }
				// return
				return res.sendStatus(200);
			});
		}
	}
});

/*** LOGOUT A USER ***/
app.post(&apos;/api/logout&apos;, (req, res, next) =&gt; {
	res.clearCookie(&apos;connect.sid&apos;); 
	req.logout(function(err) {
		console.log(&apos;logged out&apos;);
		console.log(err)
		req.session.destroy(function (err) {
			res.send();
		});
	});
});</code></pre><p>The above routes handle the client post actions when the user signs up, logs in, or logs out. &#xA0;They utilize the passport scripts we created and sends authentication data back to the client.</p><p>Since, we&apos;ve added new npm modules, we&apos;ll need to install them. &#xA0;From our terminal within the <code>server</code> directory, install them by running the following:</p><pre><code>npm install  express-session connect-mongo passport passport-local bcrypt-nodejs</code></pre><p>We also need to create a session key in our <code>.env</code> file. &#xA0;This can be any secret text that you like, and adds a security layer when sending data between our server and our mongo database. &#xA0;In the example below, I am adding this under our MONGODB key created in our <a href="https://www.initialapps.com/connecting-our-expressjs-vue3-app-to-a-mongoose-database/">last tutorial</a>.</p><pre><code>MONGODB=mongodb+srv://&lt;username&gt;:&lt;password&gt;@cluster0.ltkorue.mongodb.net/?retryWrites=true&amp;w=majority
SESSION_SK = mysecretkey</code></pre><h2 id="building-our-client">Building Our Client</h2><p>Now we build our front end framework using VueJs. The frontend framework defines everything on our user&apos;s browser, including how the app looks via HTML &amp; CSS. &#xA0;It also handles our client side actions, for example when the user clicks &quot;Login,&quot; the client side will post this action to our server.</p><p>Now, we&apos;ll be working from our <code>client</code> directory. &#xA0;First, we&apos;ll be using the <code>vue3-cookies</code> module to get our authentication cookie on the client. &#xA0;Let&apos;s install this now. &#xA0;From our <code>client</code> directory, run the following:</p><pre><code>npm install vue3-cookies --save</code></pre><p>Next, open the <code>App.vue</code> file and rewrite the entire file as follows:</p><pre><code>&lt;template&gt;
	&lt;div&gt;
		&lt;main class=&quot;main&quot;&gt;
			&lt;form v-if=&quot;!authenticated&quot;&gt;
				&lt;input
					type=&quot;text&quot;
					placeholder=&quot;Email Address&quot;
					v-model=&quot;email&quot;
				/&gt;
				&lt;input
					type=&quot;password&quot;
					placeholder=&quot;Password&quot;
					v-model=&quot;password&quot;
				/&gt;
				&lt;input type=&quot;submit&quot; value=&quot;Sign Up&quot; @click.prevent=&quot;onSignup&quot;/&gt;
				&lt;input type=&quot;submit&quot; value=&quot;Login&quot; @click.prevent=&quot;onLogin&quot; /&gt;
			&lt;/form&gt;
			&lt;form @submit.prevent=&quot;onLogout&quot; v-if=&quot;authenticated&quot;&gt;
				&lt;h1&gt;You&apos;re Logged In!&lt;/h1&gt;
				&lt;br&gt;
				&lt;input type=&quot;submit&quot; value=&quot;Logout&quot; /&gt;
			&lt;/form&gt;
		&lt;/main&gt;
	&lt;/div&gt;
&lt;/template&gt;

&lt;script lang=&quot;ts&quot;&gt;
	import { defineComponent, onMounted, ref } from &apos;vue&apos;
	import axios from &apos;axios&apos;
	import { useCookies } from &quot;vue3-cookies&quot;;

	axios.defaults.withCredentials = true
	export default defineComponent({
		setup() {
			const { cookies } = useCookies() as any
			return { cookies }
		},
		data() {
			return {
				email: &quot;&quot; as string,
				password: &quot;&quot; as string,
				result: &quot;&quot; as string,
				authenticated: false as boolean,
			}
		},
		computed: {
			checkCookie() {
				if (this.cookies.get(&apos;connect.sid&apos;)) {
					return true
				} else {
					return false
				}
			},
		},
		methods: {
			onSignup() {
				try {
				axios
				.post(&quot;/api/signup&quot;, { 
					email: this.email,
					password: this.password,
				})
				.then((response) =&gt; {
					this.authenticated = true
				}, (error) =&gt; {
					throw error || new Error(`Request failed`);
				})
				} catch(error:any) {
				}
			},
			onLogin() {
				try {
				axios
				.post(&quot;/api/login&quot;, { 
					email: this.email,
					password: this.password,
				})
				.then((response) =&gt; {
					this.authenticated = true
				}, (error) =&gt; {
					throw error || new Error(`Request failed`);
				})
				} catch(error:any) {
					// Consider implementing your own error handling logic here
					alert(error.message);
				}
			},
			onLogout() {
				try {
					axios
					.post(&quot;/api/logout&quot;)
					.then((response) =&gt; {
						this.authenticated = false
					}, (error) =&gt; {
						throw error || new Error(`Request failed`);
					})
				} catch(error:any) {
					// Consider implementing your own error handling logic here
					console.error(error);
					alert(error.message);
				}
			},
		},
		mounted(){
			this.authenticated = this.checkCookie
		}
	});
&lt;/script&gt;

&lt;style&gt;
  @font-face {
    font-family: &quot;ColfaxAI&quot;;
    src: url(https://cdn.openai.com/API/fonts/ColfaxAIRegular.woff2)
        format(&quot;woff2&quot;),
      url(https://cdn.openai.com/API/fonts/ColfaxAIRegular.woff) format(&quot;woff&quot;);
    font-weight: normal;
    font-style: normal;
  }
  @font-face {
    font-family: &quot;ColfaxAI&quot;;
    src: url(https://cdn.openai.com/API/fonts/ColfaxAIBold.woff2) format(&quot;woff2&quot;),
      url(https://cdn.openai.com/API/fonts/ColfaxAIBold.woff) format(&quot;woff&quot;);
    font-weight: bold;
    font-style: normal;
  }
  .main,
  .main input {
    font-size: 16px;
    line-height: 24px;
    color: #353740;
    font-family: &quot;ColfaxAI&quot;, Helvetica, sans-serif;
  }
  .main {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding-top: 60px;
  }
  .main .icon {
    width: 34px;
  }
  .main h3 {
    font-size: 32px;
    line-height: 40px;
    font-weight: bold;
    color: #202123;
    margin: 16px 0 40px;
  }
  .main form {
    display: flex;
    flex-direction: column;
    width: 320px;
  }
  .main input {
    padding: 12px 16px;
    border: 1px solid #10a37f;
    border-radius: 4px;
    margin-bottom: 24px;
    outline-color: #10a37f;
  }
  .main ::placeholder {
    color: #8e8ea0;
    opacity: 1;
  }
  .main input[type=&quot;submit&quot;] {
    padding: 12px 0;
    color: #fff;
    background-color: #10a37f;
    border: none;
    border-radius: 4px;
    text-align: center;
    cursor: pointer;
  }
  .main .result {
    font-weight: bold;
    margin-top: 40px;
  }
&lt;/style&gt;</code></pre><p>Let&apos;s explain the above. &#xA0;The first major change is our login form:</p><pre><code>&lt;form v-if=&quot;!authenticated&quot;&gt;
    &lt;input
        type=&quot;text&quot;
        placeholder=&quot;Email Address&quot;
        v-model=&quot;email&quot;
    /&gt;
    &lt;input
        type=&quot;password&quot;
        placeholder=&quot;Password&quot;
        v-model=&quot;password&quot;
    /&gt;
    &lt;input type=&quot;submit&quot; value=&quot;Sign Up&quot; @click.prevent=&quot;onSignup&quot;/&gt;
    &lt;input type=&quot;submit&quot; value=&quot;Login&quot; @click.prevent=&quot;onLogin&quot; /&gt;
&lt;/form&gt;
&lt;form @submit.prevent=&quot;onLogout&quot; v-if=&quot;authenticated&quot;&gt;
    &lt;h1&gt;You&apos;re Logged In!&lt;/h1&gt;
    &lt;br&gt;
    &lt;input type=&quot;submit&quot; value=&quot;Logout&quot; /&gt;
&lt;/form&gt;</code></pre><p>We&apos;ve added an HTML form that takes both the email and password inputs. &#xA0;We&apos;ve also added a signup and login button. &#xA0;Below that is another form intended for logged in users, with only one button to log out. &#xA0;Both forms use the <code>v-if</code> functionality provided by VueJs to check against an <code>authenticated</code> variable. &#xA0;If the user is not authenticated, it shows the first login/signup form, and if the user is authenticated, it shows the second logout form. &#xA0; </p><h3 id="implementing-vue3-cookies">Implementing Vue3 Cookies</h3><p>The next major highlight is the use of <code>vue3-cookies</code>:</p><pre><code>import { useCookies } from &quot;vue3-cookies&quot;;

axios.defaults.withCredentials = true
export default defineComponent({
    setup() {
        const { cookies } = useCookies() as any
        return { cookies }
    },</code></pre><p>Per the <code>vue3-cookies</code> documentation, we are importing this module and making it available to our app during <code>setup()</code>. When a user logs in, our server sends a cookie to the client. It&apos;s important for our client to search for this cookie, especially on hard reloads, to see if we&apos;ve already logged in. &#xA0;Hence, later in this file we add a computed property to check for authentication by cookie:</p><pre><code>computed: {
    checkCookie() {
        if (this.cookies.get(&apos;connect.sid&apos;)) {
            return true
        } else {
            return false
        }
    },
},</code></pre><p>We utilize this function whenever our app mounts, which is added later in our script as follows:</p><pre><code>mounted(){
    this.authenticated = this.checkCookie
}</code></pre><p>The above check happens whenever the app mount, such as during a hard reload. Hard reload will clear most of our app data but does not clear cookies, hence we can check for authentication if our cookie is still present.</p><h3 id="adding-our-client-data">Adding Our Client Data</h3><p>Next, we define the variables that our client will use. &#xA0;In our case: <code>email</code>, <code>password</code>, and <code>authenticated</code>. &#xA0;We can see these variables sprinkled into our app&apos;s HTML forms that we wrote earlier. </p><pre><code>data() {
    return {
        email: &quot;&quot; as string,
        password: &quot;&quot; as string,
        authenticated: false as boolean,
    }
},</code></pre><h3 id="handling-user-actions">Handling User Actions</h3><p>Finally, we add the javascript functions on the client for handling user signups, logins, and logouts. &#xA0;You can also find these functions defined on our HTML form buttons that we wrote earlier. &#xA0;These functions post the necessary user data to our server routes.</p><pre><code>methods: {
    onSignup() {
        try {
        axios
        .post(&quot;/api/signup&quot;, { 
            email: this.email,
            password: this.password,
        })
        .then((response) =&gt; {
            this.authenticated = true
        }, (error) =&gt; {
            throw error || new Error(`Request failed`);
        })
        } catch(error:any) {
        }
    },
    onLogin() {
        try {
        axios
        .post(&quot;/api/login&quot;, { 
            email: this.email,
            password: this.password,
        })
        .then((response) =&gt; {
            this.authenticated = true
        }, (error) =&gt; {
            throw error || new Error(`Request failed`);
        })
        } catch(error:any) {
            // Consider implementing your own error handling logic here
            alert(error.message);
        }
    },
    onLogout() {
        try {
            axios
            .post(&quot;/api/logout&quot;)
            .then((response) =&gt; {
                this.authenticated = false
            }, (error) =&gt; {
                throw error || new Error(`Request failed`);
            })
        } catch(error:any) {
            // Consider implementing your own error handling logic here
            console.error(error);
            alert(error.message);
        }
    },
},</code></pre><h2 id="with-that-your-authentication-app-is-ready-to-go">With that, your authentication app is ready to go!</h2><p>Let&apos;s restart our client and server scripts that we ran earlier (press <code>ctrl c</code> to stop any existing scripts). &#xA0;</p><p>From the <code>client</code> directory, run:</p><pre><code>npm run dev</code></pre><p>From the <code>server</code> directory, run:</p><pre><code>node server.js</code></pre><p>And, we should then see our app in action:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.initialapps.com/content/images/2023/07/image-13.png" class="kg-image" alt="Adding App Authentication with PassportJs on a MEVN Stack" loading="lazy" width="580" height="460"><figcaption>When Signed Out</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.initialapps.com/content/images/2023/07/image-14.png" class="kg-image" alt="Adding App Authentication with PassportJs on a MEVN Stack" loading="lazy" width="536" height="292"><figcaption>When Logged In</figcaption></figure><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">Git Repo Here:<a href="https://github.com/Initial-Apps/vue3-mevn-authentication"> https://github.com/Initial-Apps/vue3-mevn-authentication</a></div></div>]]></content:encoded></item><item><title><![CDATA[How to Properly Logout using PassportJs, Express-Session, on a Single Page App]]></title><description><![CDATA[Proper logout using PassportJs & Express-Session for single page web application, clearing both the client cookie and database session.]]></description><link>https://www.initialapps.com/properly-logout-passportjs-express-session-for-single-page-app/</link><guid isPermaLink="false">64a79dbf466557050e80ded1</guid><category><![CDATA[Tutorials]]></category><dc:creator><![CDATA[Andrew Akagawa]]></dc:creator><pubDate>Fri, 07 Jul 2023 05:55:05 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1581553673739-c4906b5d0de8?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDJ8fHBhc3Nwb3J0fGVufDB8fHx8MTY4ODcwOTIwNnww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1581553673739-c4906b5d0de8?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDJ8fHBhc3Nwb3J0fGVufDB8fHx8MTY4ODcwOTIwNnww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="How to Properly Logout using PassportJs, Express-Session, on a Single Page App"><p>A short explanation for how to properly logout using <a href="https://www.passportjs.org/">PassportJs</a>, <a href="https://www.npmjs.com/package/express-session">Express-Session</a> for single page web application. &#xA0;This short tutorial will share how to fully logout server side and clear the client side cookie as well.</p><h2 id="the-short-answer">The short answer:</h2><pre><code>/*** LOGOUT A USER ***/
app.post(&apos;/api/logout&apos;, (req, res, next) =&gt; {
	res.clearCookie(&apos;connect.sid&apos;);  // clear the session cookie
	req.logout(function(err) {  // logout of passport
		req.session.destroy(function (err) { // destroy the session
			res.send(); // send to the client
		});
	});
});</code></pre><h2 id="a-thorough-explanation">A thorough explanation:</h2><p>While the documentation for PassportJs and Express Session are relatively clear for login in a user, I could not find great documentation for how these all worked together to logout a user. &#xA0;Hence, I&apos;m not sure if the above is the official and proper way to logout, and only arrived at this by much trial and error. &#xA0;My goal was to clear the session on both the server and client. &#xA0;So, I&apos;ll walk you through a short summary of my tests and findings! &#x1F913;</p><h3 id="logging-out-with-the-passportjs-logout-function">Logging out with the PassportJs logout function </h3><pre><code>app.post(&apos;/logout&apos;, function(req, res, next){
  req.logout(function(err) {
    if (err) { return next(err); }
    res.redirect(&apos;/&apos;);
  });
});</code></pre><p>PassportJs provides the example above, but this won&apos;t work for my single page web app, as I want to avoid using <code>res.redirect(&apos;/&apos;)</code>. So below is my first re-write:</p><pre><code>/*** LOGOUT A USER ***/
app.post(&apos;/api/logout&apos;, (req, res, next) =&gt; {
	req.logout(function(err) {
		console.log(err)
	});
});</code></pre><p>Using <code>req.logout</code> will logout the user and clear the cookie on our database, but it does not clear that pesky cookie on the client. &#xA0;You can find this cookie by opening google developer console in your browser and clicking the <code>applications</code> tab.</p><figure class="kg-card kg-image-card"><img src="https://www.initialapps.com/content/images/2023/07/image-9.png" class="kg-image" alt="How to Properly Logout using PassportJs, Express-Session, on a Single Page App" loading="lazy" width="1100" height="375" srcset="https://www.initialapps.com/content/images/size/w600/2023/07/image-9.png 600w, https://www.initialapps.com/content/images/size/w1000/2023/07/image-9.png 1000w, https://www.initialapps.com/content/images/2023/07/image-9.png 1100w" sizes="(min-width: 720px) 720px"></figure><h2 id="adding-expressjs-clear-cookie-and-sending">Adding ExpressJs Clear Cookie and sending</h2><p>Next, I am adding <code>res.clearCookie()</code> provided by <a href="https://expressjs.com/en/api.html#res.clearCookie">ExpressJs</a>. &#xA0;I am also creating an empty send response to update our client (see code comments below):</p><pre><code>
/*** LOGOUT A USER ***/
app.post(&apos;/api/logout&apos;, (req, res, next) =&gt; {
	res.clearCookie(&apos;connect.sid&apos;);  // clear the cookie
	req.logout(function(err) {
		console.log(err)
		res.send(); // send to the client
	});
});</code></pre><p>Interestingly, this also clears the cookie on the database, but just keeps updating our client side cookie to something new?</p><figure class="kg-card kg-image-card"><img src="https://www.initialapps.com/content/images/2023/07/image-11.png" class="kg-image" alt="How to Properly Logout using PassportJs, Express-Session, on a Single Page App" loading="lazy" width="1056" height="311" srcset="https://www.initialapps.com/content/images/size/w600/2023/07/image-11.png 600w, https://www.initialapps.com/content/images/size/w1000/2023/07/image-11.png 1000w, https://www.initialapps.com/content/images/2023/07/image-11.png 1056w" sizes="(min-width: 720px) 720px"></figure><h2 id="finally-lets-destroy-the-session-in-express-session">Finally, let&apos;s Destroy the Session in Express-Session</h2><p>Express-session has a function to <a href="https://github.com/expressjs/session#readme">destroy</a> our session. &#xA0;Let&apos;s then destroy our session prior to sending to the client, as shown in the commented code below.</p><pre><code>/*** LOGOUT A USER ***/
app.post(&apos;/api/logout&apos;, (req, res, next) =&gt; {
	res.clearCookie(&apos;connect.sid&apos;); 
	req.logout(function(err) {
		console.log(err)
		req.session.destroy(function (err) { // destroys the session
			res.send();
		});
	});
});</code></pre><p>After firing up our server again, we see that the above code clears the session in our database. &#xA0;It also clears our cookie client side (Hooray!).</p><figure class="kg-card kg-image-card"><img src="https://www.initialapps.com/content/images/2023/07/image-12.png" class="kg-image" alt="How to Properly Logout using PassportJs, Express-Session, on a Single Page App" loading="lazy" width="1035" height="266" srcset="https://www.initialapps.com/content/images/size/w600/2023/07/image-12.png 600w, https://www.initialapps.com/content/images/size/w1000/2023/07/image-12.png 1000w, https://www.initialapps.com/content/images/2023/07/image-12.png 1035w" sizes="(min-width: 720px) 720px"></figure>]]></content:encoded></item><item><title><![CDATA[Connecting our ExpressJs/Vue3 App to a Mongoose Database]]></title><description><![CDATA[<p>A starter example tutorial for connecting ExpressJs to a Mongoose database hosted on MongoDB.</p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">Git Repo Here: https://github.com/Initial-Apps/vue3-express-mongo</div></div><h2 id="prerequisites">Prerequisites</h2><p>Here&apos;s what you will need in order to get started:</p><ul><li>Setup a <a href="https://www.mongodb.com/atlas/database">MongoDB</a> account to host your database.</li><li>We will continue on the simple</li></ul>]]></description><link>https://www.initialapps.com/connecting-our-expressjs-vue3-app-to-a-mongoose-database/</link><guid isPermaLink="false">64a1afa2466557050e80dd08</guid><dc:creator><![CDATA[Andrew Akagawa]]></dc:creator><pubDate>Sun, 02 Jul 2023 19:34:37 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1552071379-041b32707fed?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDV8fG1vbmdvb3NlfGVufDB8fHx8MTY4ODMyNjQ4Nnww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1552071379-041b32707fed?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDV8fG1vbmdvb3NlfGVufDB8fHx8MTY4ODMyNjQ4Nnww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Connecting our ExpressJs/Vue3 App to a Mongoose Database"><p>A starter example tutorial for connecting ExpressJs to a Mongoose database hosted on MongoDB.</p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">Git Repo Here: https://github.com/Initial-Apps/vue3-express-mongo</div></div><h2 id="prerequisites">Prerequisites</h2><p>Here&apos;s what you will need in order to get started:</p><ul><li>Setup a <a href="https://www.mongodb.com/atlas/database">MongoDB</a> account to host your database.</li><li>We will continue on the simple starter <a href="https://www.initialapps.com/express-vue3-vite/">Vue3/ExpressJS app</a> from our prior post.</li></ul><h2 id="getting-started">Getting Started</h2><p>First, let&apos;s clone our prior Vue3/ExpressJS app and rename it. &#xA0;Run the following:</p><pre><code>git clone git@github.com:Initial-Apps/vue3-express.git vue3-express-mongo</code></pre><p>Now, let&apos;s move into our app and install our modules on our <code>server</code>:</p><pre><code>cd vue3-express-mongo/server
npm install</code></pre><p>Next, let&apos;s start the server to make sure all is working. &#xA0;Run the following:</p><pre><code>node server.js</code></pre><h2 id="setting-up-mongodb">Setting up MongoDB</h2><p>Login to your MongoDB account: <a href="https://cloud.mongodb.com/">https://cloud.mongodb.com/</a></p><p>Select <code>New Project</code> and name your project. You can add members if you like. Then click <code>Create Project</code>.</p><p>Next, continue to <code>Build a Database</code>.</p><figure class="kg-card kg-image-card"><img src="https://www.initialapps.com/content/images/2023/07/image-1.png" class="kg-image" alt="Connecting our ExpressJs/Vue3 App to a Mongoose Database" loading="lazy" width="824" height="422" srcset="https://www.initialapps.com/content/images/size/w600/2023/07/image-1.png 600w, https://www.initialapps.com/content/images/2023/07/image-1.png 824w" sizes="(min-width: 720px) 720px"></figure><p>Select whatever plan you like and click <code>create</code>.</p><p>Next, create a username and password for your database, and be sure to save this info.</p><figure class="kg-card kg-image-card"><img src="https://www.initialapps.com/content/images/2023/07/image-2.png" class="kg-image" alt="Connecting our ExpressJs/Vue3 App to a Mongoose Database" loading="lazy" width="1003" height="771" srcset="https://www.initialapps.com/content/images/size/w600/2023/07/image-2.png 600w, https://www.initialapps.com/content/images/size/w1000/2023/07/image-2.png 1000w, https://www.initialapps.com/content/images/2023/07/image-2.png 1003w" sizes="(min-width: 720px) 720px"></figure><p>Then, select connect from <code>My Local Environment</code> (see below):</p><figure class="kg-card kg-image-card"><img src="https://www.initialapps.com/content/images/2023/07/image-3.png" class="kg-image" alt="Connecting our ExpressJs/Vue3 App to a Mongoose Database" loading="lazy" width="1021" height="371" srcset="https://www.initialapps.com/content/images/size/w600/2023/07/image-3.png 600w, https://www.initialapps.com/content/images/size/w1000/2023/07/image-3.png 1000w, https://www.initialapps.com/content/images/2023/07/image-3.png 1021w" sizes="(min-width: 720px) 720px"></figure><p>Finally, select <code>Finish and Close</code>. Hooray, we&apos;ve setup our database! &#x1F913;</p><h2 id="connecting-expressjs-to-our-mongodb">Connecting ExpressJs to our MongoDB</h2><p>Now let&apos;s work on our server to connect ExpressJs to MongoDB. To do this, we&apos;ll work on our server middleware file <code>server/server.js</code>. Let&apos;s open this file and get to work. &#xA0;</p><p>I&apos;m adding the following code to <code>server.js</code> just after defining our imported modules. I&apos;ve added code comments as well.</p><pre><code>require(&apos;dotenv&apos;).config(); // needed to read our environment variables
const mongoose = require(&apos;mongoose&apos;);  // We need the mongoose module
const connectString = process.env.MONGODB;  // This holds our username and password, stored as a environment variable
mongoose.connect(connectString, { useNewUrlParser: true }).then(
  () =&gt; { 
    console.log (&apos;Succeeded connected to database&apos;);
   },
  err =&gt; { 
    console.log (&apos;ERROR connecting to database: &apos; + err);
   }
);</code></pre><p>Here&apos;s what the above code does:</p><ul><li>Adds the <code>dotenv</code> module so that we can store our MongoDB username and password as an environment variable. &#xA0;This is preferred over placing this sensitive information directly in your code.</li><li>Adds the <code>mongoose</code> module for connecting to our MongoDB database</li><li>The <code>connectString</code> variable will hold our login credentials for MongoDB. &#xA0;We&apos;ll set this up next.</li><li>Finally, we add the connect script to connect to our DB.</li></ul><p>Next, we have to setup our login and password for MongoDB as an environment variable. &#xA0;You do not want to ever share this file because it has sensitive login information. &#xA0;That said, I&apos;ve created a <code>example_env.txt</code> file in our <a href="https://github.com/Initial-Apps/vue3-express-mongo">github repo</a> to provide template. &#xA0;</p><p>We make sure our environment credentials are hidden by opening your <code>.gitignore</code> file, adding the following to the end of the file and save:</p><pre><code># env files
.env</code></pre><p>Next, we&apos;ll get our connect string from MongoDB. You can find your connect string format on your MongoDB dashboard. &#xA0;Click <code>connect</code> and then connect by Drivers.</p><figure class="kg-card kg-image-card"><img src="https://www.initialapps.com/content/images/2023/07/image-4.png" class="kg-image" alt="Connecting our ExpressJs/Vue3 App to a Mongoose Database" loading="lazy" width="1037" height="417" srcset="https://www.initialapps.com/content/images/size/w600/2023/07/image-4.png 600w, https://www.initialapps.com/content/images/size/w1000/2023/07/image-4.png 1000w, https://www.initialapps.com/content/images/2023/07/image-4.png 1037w" sizes="(min-width: 720px) 720px"></figure><p>We are going to add our key using <code>mongoose</code> so we only need to pay attention to the <strong>Driver</strong> and the <strong>Connection String</strong>.</p><p>Select <code>Node</code> for the Driver like so:</p><figure class="kg-card kg-image-card"><img src="https://www.initialapps.com/content/images/2023/07/image-5.png" class="kg-image" alt="Connecting our ExpressJs/Vue3 App to a Mongoose Database" loading="lazy" width="639" height="195" srcset="https://www.initialapps.com/content/images/size/w600/2023/07/image-5.png 600w, https://www.initialapps.com/content/images/2023/07/image-5.png 639w"></figure><p>Then copy and paste your Connection String:</p><figure class="kg-card kg-image-card"><img src="https://www.initialapps.com/content/images/2023/07/image-6.png" class="kg-image" alt="Connecting our ExpressJs/Vue3 App to a Mongoose Database" loading="lazy" width="959" height="201" srcset="https://www.initialapps.com/content/images/size/w600/2023/07/image-6.png 600w, https://www.initialapps.com/content/images/2023/07/image-6.png 959w" sizes="(min-width: 720px) 720px"></figure><p>Now create a file named <code>.env</code> and add your connect URL. The contents of the file should look something like this (replacing &lt;username&gt; and &lt;password&gt; with the MongoDB username and password we created in the last section). Save the file once this is added.</p><pre><code>MONGODB=mongodb+srv://&lt;username&gt;:&lt;password&gt;@cluster0.ltkorue.mongodb.net/?retryWrites=true&amp;w=majority</code></pre><p>Then we install the modules we need for the connection. &#xA0;From the terminal window that we used to run our node server (the one that we ran <code>node server.js</code>). &#xA0;First stop the server (hit <code>ctrl+c</code>). &#xA0;Now install the modules:</p><pre><code>npm install mongoose dotenv</code></pre><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text"><strong>Avoiding pitfalls:</strong> Be sure you are using the latest node vs needed for mongoose. I&apos;m using Node v18.12.1 and Mongooose v^7.3.1.</div></div><p></p><p>Now we can restart our server by running:</p><pre><code>node server.js</code></pre><p>If all runs properly, you should see the following on your console:</p><figure class="kg-card kg-image-card"><img src="https://www.initialapps.com/content/images/2023/07/image-8.png" class="kg-image" alt="Connecting our ExpressJs/Vue3 App to a Mongoose Database" loading="lazy" width="340" height="45"></figure><p>Congrats as you are now connected to your database!</p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">Git Repo Here: https://github.com/Initial-Apps/vue3-express-mongo</div></div><h2 id="whats-next">What&apos;s Next</h2><p>Next, we need to start using your new database for various data! &#xA0;Want to see how we do that? &#xA0;Stay tuned or subscribe as we continue this app.</p>]]></content:encoded></item><item><title><![CDATA[ChatGPT OpenAI Example Starter Project using Vue3 and Express]]></title><description><![CDATA[A tutorial for getting started with OpenAI's API, based on their quickstart guide, but coded in Vue3 and ExpressJs.]]></description><link>https://www.initialapps.com/chatgpt-openai-example-starter-project-using-vue3-and-express/</link><guid isPermaLink="false">649a5d6b466557050e80da21</guid><dc:creator><![CDATA[Andrew Akagawa]]></dc:creator><pubDate>Fri, 30 Jun 2023 05:28:18 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1679083216051-aa510a1a2c0e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fGNoYXRncHR8ZW58MHx8fHwxNjg4MDA4NjIxfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1679083216051-aa510a1a2c0e?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fGNoYXRncHR8ZW58MHx8fHwxNjg4MDA4NjIxfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="ChatGPT OpenAI Example Starter Project using Vue3 and Express"><p>AI is hot these days, and OpenAI has an excellent and powerful API for all to use! They have a nice tutorial for getting started, with a source repo built in ReactJs. I wanted to do this with Vue3 and share with the world. So without further ado, here&apos;s how to setup a starter app with OpenAI using Vue3 and Expressjs.</p><p>You can find more about <a href="https://github.com/openai/openai-quickstart-node">OpenAI&apos;s quickstart guide example here</a>. &#xA0;This example app takes in an animal and spits out 3 suggestions for a pet name. &#xA0;The quickstart app is built on ReactJs and Nextjs. This guide will rewrite the quickstart guide using Vue3 and ExpressJs. &#xA0;Why, because I really like Vue3! &#x1F913;</p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">Git repo here: https://github.com/Initial-Apps/openai-quickstart-vue3</div></div><h2 id="prerequisite-setting-up-an-app-with-vue3-and-expressjs">Prerequisite: Setting up an app with Vue3 and ExpressJs</h2><p>This tutorial picks up from where our last tutorial left off. &#xA0;If you want to setup your app from scratch, please see the below link:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.initialapps.com/express-vue3-vite/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Setting up Vue3, Vite, and Express</div><div class="kg-bookmark-description">A simple example setup of Vue3 using Vite, with ExpressJS and NodeJS on the backend.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://s3-us-west-1.amazonaws.com/initialapps/2018/06/favicon-256-1.png" alt="ChatGPT OpenAI Example Starter Project using Vue3 and Express"><span class="kg-bookmark-author">Initial Apps</span><span class="kg-bookmark-publisher">Andrew Akagawa</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://images.unsplash.com/photo-1561883088-039e53143d73?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fHZ1ZXxlbnwwfHx8fDE2ODc5MzI5MTF8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="ChatGPT OpenAI Example Starter Project using Vue3 and Express"></div></a></figure><p>Otherwise, you can skip ahead by cloning the repo from the link below:</p><pre><code>git clone https://github.com/Initial-Apps/vue3-express.git</code></pre><h2 id="setting-up-the-client-side">Setting up the client side</h2><p>Let&apos;s first start with some housekeeping. We&apos;ll give our app a new directory name and then move into our new repo:</p><pre><code>mv vue3-express openai-quickstart-vue3
cd openai-quickstart-vue3</code></pre><p>Now let&apos;s start the client side. &#xA0;Move into your new apps client folder, install our modules, and run the dev script:</p><pre><code>cd client
npm install
npm run dev</code></pre><p>Hooray, we should now have a Vue starter app running! Navigate to <a href="http://localhost:5173/">http://localhost:5173/</a> to see it.</p><h2 id="setting-up-our-openai-credentials">Setting up our OpenAI credentials</h2><p>Now lets start to work on on server. &#xA0;Open a new terminal, move in the server folder, and install our npm modules.</p><pre><code>cd server
npm install</code></pre><p>Next, let&apos;s make sure our environment credentials are hidden. &#xA0;In the <code>server</code> directory, open your <code>.gitignore</code> file, add the following to the end of the file and save:</p><pre><code># env files
.env</code></pre><p>This will keep your API credentials hidden to your git repo.</p><p>Next, create a new file and name it <code>.env</code>. &#xA0;Copy and paste the following into your <code>.env</code> file. &#xA0;Add your <a href="https://platform.openai.com/account/api-keys">OpenAPI key</a>. &#xA0;</p><pre><code># Do not share your OpenAI API key with anyone! It should remain a secret.
OPENAI_API_KEY=</code></pre><h2 id="setting-up-expressjs">Setting up ExpressJs</h2><p>Now, let&apos;s setup our server script. &#xA0;Open the <code>server.js</code> file and rewrite as follows:</p><pre><code>const express = require(&apos;express&apos;)
const app = express()
const port = 3000
const bodyParser = require(&apos;body-parser&apos;);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

require(&apos;dotenv&apos;).config(); // needed to read our environment variables

// the next 3 const integrate the openai API
const {Configuration, OpenAIApi} =  require(&quot;openai&quot;);
const configuration = new Configuration({
  apiKey: process.env.OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);

// this post function is a taken from openai quickstart example, but modified to work with expressjs
app.post(&apos;/api/generator&apos;, async (req, res) =&gt; {
  if (!configuration.apiKey) {
      res.status(500).json({
          error: {
              message: &quot;OpenAI API key not configured, please follow instructions in README.md&quot;,
          }
      });
      return;
  }

  const animal = req.body.animal || &apos;&apos;;
  if (animal.trim().length === 0) {
      res.status(400).json({
          error: {
              message: &quot;Please enter a valid animal&quot;,
          }
      });
      return;
  }

  try {
      const completion = await openai.createCompletion({
          model: &quot;text-davinci-003&quot;,
          prompt: generatePrompt(animal),
          temperature: 0.6,
      });
      res.status(200).json({ result: completion.data.choices[0].text });
  } catch(error) {
      // Consider adjusting the error handling logic for your use case
      if (error.response) {
          console.error(error.response.status, error.response.data);
          res.status(error.response.status).json(error.response.data);
      } else {
          console.error(`Error with OpenAI API request: ${error.message}`);
          res.status(500).json({
              error: {
              message: &apos;An error occurred during your request.&apos;,
              }
          });
      }
  }
  function generatePrompt(animal) {
  const capitalizedAnimal =
      animal[0].toUpperCase() + animal.slice(1).toLowerCase();
      return `Suggest three names for an animal that is a superhero.

      Animal: Cat
      Names: Captain Sharpclaw, Agent Fluffball, The Incredible Feline
      Animal: Dog
      Names: Ruff the Protector, Wonder Canine, Sir Barks-a-Lot
      Animal: ${capitalizedAnimal}
      Names:`;
  }    
})

app.listen(port, () =&gt; {
  console.log(`Example app listening on port ${port}`)
})</code></pre><p>This updated code adds the openai generator route to our server script. The <code>generator</code> route is basically a rewrite of the the <a href="https://github.com/openai/openai-node">quickstart example</a> provided by OpenAI, so that it works with ExpressJs. The server script makes all the API calls to OpenAI, so feel free to come back to this later and play around with it for fun!</p><p>We also must install the OpenAI API, and <code>dotenv</code> to read the access keys in our <code>.env</code> folder. &#xA0;We do this by running the following install scripts:</p><pre><code>npm install openai
npm install dotenv --save</code></pre><p>Now we can run our server with the foll0wing command from the server directory to start our sever:</p><pre><code>node server.js</code></pre><h2 id="finally-lets-rewrite-the-vuejs-frontend">Finally, let&apos;s rewrite the VueJs frontend</h2><p>First, let&apos;s rewrite our <code>App.vue</code> file. &#xA0;You can find this in the <code>client\src\</code> directory. Basically, we are rewriting the OpenAI <a href="https://github.com/openai/openai-node">quickstart example</a> from React to Vue as follows:</p><pre><code>&lt;template&gt;
  &lt;div&gt;
    &lt;Head&gt;
      &lt;title&gt;OpenAI Quickstart&lt;/title&gt;
      &lt;link rel=&quot;icon&quot; href=&quot;/dog.png&quot; /&gt;
    &lt;/Head&gt;

    &lt;main class=&quot;main&quot;&gt;
      &lt;h3&gt;Name my pet&lt;/h3&gt;
      &lt;form @submit.prevent=&quot;onSubmit&quot;&gt;
        &lt;input
          type=&quot;text&quot;
          name=&quot;animal&quot;
          placeholder=&quot;Enter an animal&quot;
          v-model=&quot;animalInput&quot;
        /&gt;
        &lt;input type=&quot;submit&quot; value=&quot;Generate names&quot; /&gt;
      &lt;/form&gt;
      &lt;div class=&quot;result&quot;&gt;{{result}}&lt;/div&gt;
    &lt;/main&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;script lang=&quot;ts&quot;&gt;
	import { defineComponent, onMounted, ref } from &apos;vue&apos;
  import axios from &apos;axios&apos;

	export default defineComponent({
		data() {
			return {
				animalInput: &quot;&quot; as string,
        result: &quot;&quot; as string,
			}
		},
		methods: {
			onSubmit() {
        try {
          axios
          .post(&quot;/api/generator&quot;, { animal: this.animalInput })
          .then((response) =&gt; {
            this.result = response.data.result;
          }, (error) =&gt; {
            throw error || new Error(`Request failed`);
          })
        } catch(error:any) {
          // Consider implementing your own error handling logic here
          console.error(error);
          alert(error.message);
        }
      }
		}
	});
&lt;/script&gt;

&lt;style&gt;
  @font-face {
    font-family: &quot;ColfaxAI&quot;;
    src: url(https://cdn.openai.com/API/fonts/ColfaxAIRegular.woff2)
        format(&quot;woff2&quot;),
      url(https://cdn.openai.com/API/fonts/ColfaxAIRegular.woff) format(&quot;woff&quot;);
    font-weight: normal;
    font-style: normal;
  }
  @font-face {
    font-family: &quot;ColfaxAI&quot;;
    src: url(https://cdn.openai.com/API/fonts/ColfaxAIBold.woff2) format(&quot;woff2&quot;),
      url(https://cdn.openai.com/API/fonts/ColfaxAIBold.woff) format(&quot;woff&quot;);
    font-weight: bold;
    font-style: normal;
  }
  .main,
  .main input {
    font-size: 16px;
    line-height: 24px;
    color: #353740;
    font-family: &quot;ColfaxAI&quot;, Helvetica, sans-serif;
  }
  .main {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding-top: 60px;
  }
  .main .icon {
    width: 34px;
  }
  .main h3 {
    font-size: 32px;
    line-height: 40px;
    font-weight: bold;
    color: #202123;
    margin: 16px 0 40px;
  }
  .main form {
    display: flex;
    flex-direction: column;
    width: 320px;
  }
  .main input[type=&quot;text&quot;] {
    padding: 12px 16px;
    border: 1px solid #10a37f;
    border-radius: 4px;
    margin-bottom: 24px;
    outline-color: #10a37f;
  }
  .main ::placeholder {
    color: #8e8ea0;
    opacity: 1;
  }
  .main input[type=&quot;submit&quot;] {
    padding: 12px 0;
    color: #fff;
    background-color: #10a37f;
    border: none;
    border-radius: 4px;
    text-align: center;
    cursor: pointer;
  }
  .main .result {
    font-weight: bold;
    margin-top: 40px;
  }
&lt;/style&gt;</code></pre><h2 id="congrats-for-setting-up-your-vue3-app-with-openai">Congrats for setting up your Vue3 app with OpenAI!</h2><p>You should now have a working app that harnesses the power of AI and ChatGPT (or text-davinci). &#xA0;Refresh your <a href="http://localhost:5173/">http://localhost:5173/</a> to see it. &#xA0;When you enter an animal in the text box, the AI API will give you suggestions for 3 names for this animal. &#xA0;Now that you have the initial setup, feel free play around with all the possibilities that this AI technology brings!</p><figure class="kg-card kg-image-card"><img src="https://www.initialapps.com/content/images/2023/06/pet.png" class="kg-image" alt="ChatGPT OpenAI Example Starter Project using Vue3 and Express" loading="lazy" width="970" height="570" srcset="https://www.initialapps.com/content/images/size/w600/2023/06/pet.png 600w, https://www.initialapps.com/content/images/2023/06/pet.png 970w" sizes="(min-width: 720px) 720px"></figure><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">Git repo here: https://github.com/Initial-Apps/openai-quickstart-vue3</div></div>]]></content:encoded></item><item><title><![CDATA[Setting up Vue3, Vite, and Express]]></title><description><![CDATA[A simple example setup of Vue3 using Vite, with ExpressJS and NodeJS on the backend.]]></description><link>https://www.initialapps.com/express-vue3-vite/</link><guid isPermaLink="false">649bb627466557050e80db13</guid><dc:creator><![CDATA[Andrew Akagawa]]></dc:creator><pubDate>Wed, 28 Jun 2023 06:16:07 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1561883088-039e53143d73?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fHZ1ZXxlbnwwfHx8fDE2ODc5MzI5MTF8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1561883088-039e53143d73?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fHZ1ZXxlbnwwfHx8fDE2ODc5MzI5MTF8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Setting up Vue3, Vite, and Express"><p>A quick guide to getting started with Vue3, Vite, and ExpressJS &amp; NodeJS for our backend. &#xA0;Let&apos;s jump right in!</p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">Git repo here: https://github.com/Initial-Apps/vue3-express</div></div><h2 id="first-lets-setup-vue3">First, Let&apos;s Setup Vue3</h2><p>First let&apos;s make a directory to house our app and open that directory.</p><pre><code>mkdir vue3-express
cd vue3-express</code></pre><p>Next, make sure you&apos;re up to date with node, npm, etc. and run the following.</p><!--kg-card-begin: markdown--><pre><code>npm init vue@latest
</code></pre>
<!--kg-card-end: markdown--><p>This will walk through the official Vue3 install script. &#xA0;It will ask you to name your app. I named mine <code>client</code>.</p><p>For the rest of the questions, if you don&apos;t know the answer I recommend you answer no. When done with these questions, you will have a starter Vue3 app! </p><p>Finish the install by changing into the directory and installing the npm packages (use the following code):</p><pre><code>  cd client
  npm install
  npm run dev</code></pre><p>Hooray, we should now have a Vue starter app running! Navigate to <a href="http://localhost:3000/" rel="nofollow">http://localhost:5173/</a> to see it. Mine looks like this:</p><figure class="kg-card kg-image-card"><img src="https://www.initialapps.com/content/images/2022/06/Screenshot-2022-06-20-12.42.08-PM.png" class="kg-image" alt="Setting up Vue3, Vite, and Express" loading="lazy"></figure><h2></h2><h2 id="setting-up-expressjs">Setting up ExpressJs</h2><p>Now we install ExpressJs. &#xA0;First, we should start a new terminal in our parent directory named <code>vue3-express</code>. &#xA0;Then create a new directory for our server, which I will name this directory <code>server</code>. &#xA0;We then move into our <code>server</code> directory and initialize it in npm.</p><pre><code>mkdir server
cd server
npm init</code></pre><p>Next, we &#xA0;install our needed modules: ExpressJs for our API posts, and Body-Parser to get the posted data. &#xA0;We install these by running the following.</p><pre><code>npm install express
npm install body-parser
</code></pre><p>Next, we setup our server script. &#xA0;Create a new file titled <code>server.js</code> (or whatever you want to name your server file) and add the following:</p><pre><code>const express = require(&apos;express&apos;)
const app = express()
const port = 3000
const bodyParser = require(&apos;body-parser&apos;);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));


app.post(&apos;/api&apos;, (req, res) =&gt; {
  console.log(req.body)
  res.status(200).json({ result: req.body.text });
})

app.listen(port, () =&gt; {
  console.log(`Example app listening on port ${port}`)
})</code></pre><p>The above is a very simple node server using express as a middleware to capture post data from our client.</p><p>Start the server by running:</p><pre><code>node server.js</code></pre><h2 id="finally-lets-configure-vite-and-create-a-vue3-test-form">Finally, Let&apos;s Configure Vite and Create a Vue3 Test Form</h2><p>Open a new terminal window and move back into our client directory and install Axios for post requests. Run the following:</p><pre><code>cd client
npm install axios
</code></pre><p>First, let&apos;s configure Vite to proxy our server so the client and server ports can talk to each other. &#xA0;Open <code>vite.config.ts</code> and add the the proxy info such that the entire file will now look like this:</p><pre><code>import { fileURLToPath, URL } from &apos;node:url&apos;

import { defineConfig } from &apos;vite&apos;
import vue from &apos;@vitejs/plugin-vue&apos;

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
  ],
  resolve: {
    alias: {
      &apos;@&apos;: fileURLToPath(new URL(&apos;./src&apos;, import.meta.url))
    }
  },
  server: {
		proxy: {
			&apos;/api&apos;: {
				target: &apos;http://localhost:3000&apos;,
				changeOrigin: true,
				secure: false,
				ws: true,
			},
		},
	} 
})
</code></pre><p>Next, lets completely rewrite our <code>App.vue</code> file (inside the <code>src</code> directory) so that it is a test form, to try out our post:</p><pre><code>&lt;template&gt;
  &lt;div&gt;
    &lt;main class=&quot;main&quot;&gt;
      &lt;form @submit.prevent=&quot;onSubmit&quot;&gt;
        &lt;input
          type=&quot;text&quot;
          placeholder=&quot;Enter Anything&quot;
          v-model=&quot;testInput&quot;
        /&gt;
        &lt;input type=&quot;submit&quot; value=&quot;Test Server&quot; /&gt;
      &lt;/form&gt;
      &lt;div class=&quot;result&quot;&gt;{{result}}&lt;/div&gt;
    &lt;/main&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;script lang=&quot;ts&quot;&gt;
	import { defineComponent, onMounted, ref } from &apos;vue&apos;
  import axios from &apos;axios&apos;

	export default defineComponent({
		data() {
			return {
				testInput: &quot;&quot; as string,
        result: &quot;&quot; as string,
			}
		},
		methods: {
			onSubmit() {
        try {
          axios
          .post(&quot;/api&quot;, { text: this.testInput })
          .then((response) =&gt; {
            this.result = response.data.result;
          }, (error) =&gt; {
            throw error || new Error(`Request failed`);
          })
        } catch(error:any) {
          // Consider implementing your own error handling logic here
          console.error(error);
          alert(error.message);
        }
      }
		}
	});
&lt;/script&gt;

&lt;style&gt;
  @font-face {
    font-family: &quot;ColfaxAI&quot;;
    src: url(https://cdn.openai.com/API/fonts/ColfaxAIRegular.woff2)
        format(&quot;woff2&quot;),
      url(https://cdn.openai.com/API/fonts/ColfaxAIRegular.woff) format(&quot;woff&quot;);
    font-weight: normal;
    font-style: normal;
  }
  @font-face {
    font-family: &quot;ColfaxAI&quot;;
    src: url(https://cdn.openai.com/API/fonts/ColfaxAIBold.woff2) format(&quot;woff2&quot;),
      url(https://cdn.openai.com/API/fonts/ColfaxAIBold.woff) format(&quot;woff&quot;);
    font-weight: bold;
    font-style: normal;
  }
  .main,
  .main input {
    font-size: 16px;
    line-height: 24px;
    color: #353740;
    font-family: &quot;ColfaxAI&quot;, Helvetica, sans-serif;
  }
  .main {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding-top: 60px;
  }
  .main .icon {
    width: 34px;
  }
  .main h3 {
    font-size: 32px;
    line-height: 40px;
    font-weight: bold;
    color: #202123;
    margin: 16px 0 40px;
  }
  .main form {
    display: flex;
    flex-direction: column;
    width: 320px;
  }
  .main input[type=&quot;text&quot;] {
    padding: 12px 16px;
    border: 1px solid #10a37f;
    border-radius: 4px;
    margin-bottom: 24px;
    outline-color: #10a37f;
  }
  .main ::placeholder {
    color: #8e8ea0;
    opacity: 1;
  }
  .main input[type=&quot;submit&quot;] {
    padding: 12px 0;
    color: #fff;
    background-color: #10a37f;
    border: none;
    border-radius: 4px;
    text-align: center;
    cursor: pointer;
  }
  .main .result {
    font-weight: bold;
    margin-top: 40px;
  }
&lt;/style&gt;</code></pre><p>Now, you should be able to see the new form at <a href="http://localhost:5173/">http://localhost:5173/</a>. &#xA0;When you enter information and submit the form, you should see this info log on the server and then see it sent back to the client.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.initialapps.com/content/images/2023/06/log.png" class="kg-image" alt="Setting up Vue3, Vite, and Express" loading="lazy" width="345" height="52"><figcaption>Server Log</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.initialapps.com/content/images/2023/06/test-1.png" class="kg-image" alt="Setting up Vue3, Vite, and Express" loading="lazy" width="571" height="343"><figcaption>Client Result</figcaption></figure><p>Congrats, you&apos;ve setup a new node app using Vue3, Vite, and ExpressJS! &#xA0;Thanks for reading and hope this helps. </p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">Git repo here: https://github.com/Initial-Apps/vue3-express</div></div>]]></content:encoded></item><item><title><![CDATA[Customizing bootstrap 5 with Sass! Example provided.]]></title><description><![CDATA[An example for how to create custom theme color pallets in Bootstrap 5 using Sass. This post focuses on Bootstrap's color mapping functionality.]]></description><link>https://www.initialapps.com/customizing-bootstrap-5-with-sass/</link><guid isPermaLink="false">62b0c4c2902d3246ef85e92a</guid><dc:creator><![CDATA[Andrew Akagawa]]></dc:creator><pubDate>Tue, 21 Jun 2022 13:37:59 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1464820453369-31d2c0b651af?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDJ8fGNvbG9yZnVsfGVufDB8fHx8MTY1NTgxODM1Mw&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1464820453369-31d2c0b651af?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDJ8fGNvbG9yZnVsfGVufDB8fHx8MTY1NTgxODM1Mw&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Customizing bootstrap 5 with Sass! Example provided."><p>An example for how to create custom theme color pallets in Bootstrap 5 using Sass. This post focuses on Bootstrap&apos;s color mapping functionality.</p><p>I was super happy to learn Bootstrap 5 is jQuery-less. Don&apos;t get me wrong, jQuery deserves it&apos;s place in legendary JS history, but doesn&apos;t play well with a lot of the latest frameworks today. </p><p>That said, it took me awhile to figure out how the Sass color mapping worked, so I shall share my experimentation in this post, using a new Vue app as an example! </p><p><a href="https://github.com/initialapps/bootstrap-5-sass-example">Github repo here.</a></p><h2 id="the-setup">The Setup</h2><p>First off, I&apos;m creating a new Vue 3 app with Vite to compile our Sass. Feel free to skip this section if you&apos;re not interested in setting things up and just want to see the Sass customization.</p><p>Make sure you&apos;re up to date with node, npm, etc. and run the following. </p><pre><code>npm init vue@latest</code></pre><p>If you run into any issues, you can find the official instructions for starting a Vue 3 app <a href="https://vuejs.org/guide/quick-start.html">provided here</a>. </p><p>The installer asks a series of questions, that you can answer mostly no too as we&apos;re just using this as an easy setup to compile the sass. </p><p>Name the project whatever you like (mine is bootstrap-5-sass-example). Next, move into it when created and install.</p><pre><code>cd bootstrap-5-sass-example
npm install
npm run dev</code></pre><p>Hooray, we should now have a Vue starter app running! Navigate to http://localhost:3000/ to see it. Mine looks like this:</p><figure class="kg-card kg-image-card"><img src="https://www.initialapps.com/content/images/2022/06/Screenshot-2022-06-20-12.42.08-PM.png" class="kg-image" alt="Customizing bootstrap 5 with Sass! Example provided." loading="lazy" width="2000" height="1207" srcset="https://www.initialapps.com/content/images/size/w600/2022/06/Screenshot-2022-06-20-12.42.08-PM.png 600w, https://www.initialapps.com/content/images/size/w1000/2022/06/Screenshot-2022-06-20-12.42.08-PM.png 1000w, https://www.initialapps.com/content/images/size/w1600/2022/06/Screenshot-2022-06-20-12.42.08-PM.png 1600w, https://www.initialapps.com/content/images/2022/06/Screenshot-2022-06-20-12.42.08-PM.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Now let&apos;s install bootstrap! Exit the running terminal <code>ctrl+c</code> and let&apos;s keep going:</p><pre><code>npm install --save bootstrap
npm install --save @popperjs/core</code></pre><p>Next, let&apos;s setup our Vite to compile Sass! </p><p>We start by making a scss file in our source directory. I&apos;m making the following new file &amp; path: <code>src&gt;assets&gt;sass&gt;main.scss</code>, and leaving the <code>main.scss</code> file blank for now. </p><p>Vue is currently using Vite to compile everything, so we can reference the path to our sass file in the Vite config file (<code>vite.config.ts</code>) by adding the following in the <code>export default defineConfig</code> block:</p><pre><code>  css: {
    preprocessorOptions: {
      sass: {
        additionalData: `
          @import &quot;@/assets/sass/main.scss&quot;;
        `
      }
    }
  }</code></pre><p>Also, per the Vite docs, we need to install the Sass pre-processor by running the following (<a href="https://vitejs.dev/guide/features.html#css-modules">more info provided here</a>):</p><pre><code>npm add -D sass</code></pre><p>Now we&apos;re almost Bootstrap ready! &#xA0;</p><p>Let&apos;s test this out by adding some nice buttons. I&apos;m going to grab some code from the Bootstrap website. We can add it into the main <code>App.vue</code> file in the source code (<code>src&gt;App.vue</code>). I&apos;m basically going to re-write the html <code>template</code> block inside this file with the following Bootstrap&apos;d out code:</p><pre><code>&lt;template&gt;
  &lt;div class=&quot;container mt-5&quot;&gt;
    &lt;h1&gt;Bootstrap Buttons!&lt;/h1&gt;
    &lt;div class=&quot;row&quot;&gt;
      &lt;div class=&quot;col&quot;&gt;
        &lt;button type=&quot;button&quot; class=&quot;btn btn-primary&quot;&gt;Primary&lt;/button&gt;
        &lt;button type=&quot;button&quot; class=&quot;btn btn-secondary&quot;&gt;Secondary&lt;/button&gt;
        &lt;button type=&quot;button&quot; class=&quot;btn btn-success&quot;&gt;Success&lt;/button&gt;
        &lt;button type=&quot;button&quot; class=&quot;btn btn-danger&quot;&gt;Danger&lt;/button&gt;
        &lt;button type=&quot;button&quot; class=&quot;btn btn-warning&quot;&gt;Warning&lt;/button&gt;
        &lt;button type=&quot;button&quot; class=&quot;btn btn-info&quot;&gt;Info&lt;/button&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/template&gt;</code></pre><p>We also need to import our sass file. Again, in the <code>App.vue</code> file we replace the <code>style</code> block to import our sass:</p><pre><code>&lt;style lang=&quot;scss&quot;&gt;
  @import &quot;@/assets/sass/main.scss&quot;;
&lt;/style&gt;</code></pre><h2 id="creating-the-custom-sass-file">Creating the custom Sass file</h2><p>Note, this is where things first got confusing for me. Bootstrap gives you either an option to load all of bootstrap (with no access to customization functions), or load parts of bootstrap (<a href="https://getbootstrap.com/docs/5.0/customize/sass/">see here</a>). For the life of me, I could not get Option B (including only parts of Bootstrap) to work. Since, I wanted all of bootstrap, as well as access to functions I decided to do both! e.g. I wanted to have my cake and eat it too! &#x1F382;</p><p>So here&apos;s my <code>main.scss</code> file (hacked together with both options):</p><pre><code>// Custom.scss

// 1. Include functions first (so you can manipulate colors, SVGs, calc, etc)
@import &quot;../node_modules/bootstrap/scss/functions&quot;;

// 2. Include any default variable overrides here


// 3. Include remainder of required Bootstrap stylesheets (including any separate color mode stylesheets)
@import &quot;../node_modules/bootstrap/scss/variables&quot;;
@import &quot;../node_modules/bootstrap/scss/variables-dark&quot;;

// 4. Include any default map overrides here


// 5. Import all the rest of Bootstrap 
@import &quot;../node_modules/bootstrap/scss/bootstrap&quot;;


// Then add additional custom code here</code></pre><p>Now lets run run the developer script in the terminal, to see things up and running:</p><pre><code>npm run dev</code></pre><p>It should look like this (with all the nice bootstrap colors)!</p><figure class="kg-card kg-image-card"><img src="https://www.initialapps.com/content/images/2022/06/Screenshot-2022-06-20-10.38.44-PM.png" class="kg-image" alt="Customizing bootstrap 5 with Sass! Example provided." loading="lazy" width="1160" height="497" srcset="https://www.initialapps.com/content/images/size/w600/2022/06/Screenshot-2022-06-20-10.38.44-PM.png 600w, https://www.initialapps.com/content/images/size/w1000/2022/06/Screenshot-2022-06-20-10.38.44-PM.png 1000w, https://www.initialapps.com/content/images/2022/06/Screenshot-2022-06-20-10.38.44-PM.png 1160w" sizes="(min-width: 720px) 720px"></figure><h2 id="now-lets-customize-the-sass">Now lets customize the Sass!</h2><p>Bootstrap gives us some nice functions to do this. It&apos;s important to note where things are placed in the sass file for it to work properly, so pay attention to the comments in the <code>main.cscc</code> file. &#xA0;</p><p>I&apos;m going to head over to <a href="https://coolors.co/">coolors</a> to make a random new theme color palette. Here it is!</p><figure class="kg-card kg-image-card"><img src="https://www.initialapps.com/content/images/2022/06/Screenshot-2022-06-20-10.42.45-PM.png" class="kg-image" alt="Customizing bootstrap 5 with Sass! Example provided." loading="lazy" width="2000" height="333" srcset="https://www.initialapps.com/content/images/size/w600/2022/06/Screenshot-2022-06-20-10.42.45-PM.png 600w, https://www.initialapps.com/content/images/size/w1000/2022/06/Screenshot-2022-06-20-10.42.45-PM.png 1000w, https://www.initialapps.com/content/images/size/w1600/2022/06/Screenshot-2022-06-20-10.42.45-PM.png 1600w, https://www.initialapps.com/content/images/2022/06/Screenshot-2022-06-20-10.42.45-PM.png 2393w" sizes="(min-width: 720px) 720px"></figure><p>Now let&apos;s re-write bootstrap&apos;s usual color variables (primary, secondary, etc) in the <code>main.scss</code> file, just under the 2. comment:</p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">Be sure to add under the #2. comment as order of code matters!</div></div><pre><code>// 2. Include any default variable overrides here
$primary: #D4CBE5;
$secondary:#CFC7D2;
$success: #BEA8AA;
$warning: #9E9885;
$danger: #7C7F65;</code></pre><p>Notice these re-writes are added before loading Bootstrap&apos;s variables in the 3. comments. That&apos;s because Boostrap uses the <code>!default</code> tag on all of it&apos;s variables. That meas the theme will take Bootstrap&apos;s defined value by default, if no other value has been assigned prior. </p><p>And our buttons now look like this:</p><figure class="kg-card kg-image-card"><img src="https://www.initialapps.com/content/images/2022/06/Screenshot-2022-06-20-10.53.14-PM.png" class="kg-image" alt="Customizing bootstrap 5 with Sass! Example provided." loading="lazy" width="1078" height="288" srcset="https://www.initialapps.com/content/images/size/w600/2022/06/Screenshot-2022-06-20-10.53.14-PM.png 600w, https://www.initialapps.com/content/images/size/w1000/2022/06/Screenshot-2022-06-20-10.53.14-PM.png 1000w, https://www.initialapps.com/content/images/2022/06/Screenshot-2022-06-20-10.53.14-PM.png 1078w" sizes="(min-width: 720px) 720px"></figure><h2 id="adding-our-own-classes">Adding our own classes</h2><p>Awesome! What if we want our own custom names for colors (e.g. <code>btn-lavender</code>), and with all the nice functionality, like changing shades when hovering over a button? Bootstrap has a mapping function for that! So, using <a href="https://coolors.co/4b4237-d5a021-ede7d9-a49694-736b60">coolors</a> again, here&apos;s a new palette:</p><figure class="kg-card kg-image-card"><img src="https://www.initialapps.com/content/images/2022/06/Screenshot-2022-06-20-10.56.45-PM.png" class="kg-image" alt="Customizing bootstrap 5 with Sass! Example provided." loading="lazy" width="2000" height="283" srcset="https://www.initialapps.com/content/images/size/w600/2022/06/Screenshot-2022-06-20-10.56.45-PM.png 600w, https://www.initialapps.com/content/images/size/w1000/2022/06/Screenshot-2022-06-20-10.56.45-PM.png 1000w, https://www.initialapps.com/content/images/size/w1600/2022/06/Screenshot-2022-06-20-10.56.45-PM.png 1600w, https://www.initialapps.com/content/images/2022/06/Screenshot-2022-06-20-10.56.45-PM.png 2389w" sizes="(min-width: 720px) 720px"></figure><p>Per the <a href="https://getbootstrap.com/docs/5.0/customize/sass/">bootstrap docs</a>, we can load the Bootstrap variables first then call the map-merge function to add our own new custom classes to our pallet!</p><p>First we define the custom colors after the 4. comment (under the variables and mixins):</p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">Be sure to add under the #4. comment as order of code matters!</div></div><pre><code>// 4. Include any default map overrides here
// Create your own map
$custom-colors: (
  &quot;dark-lava&quot;: #4B4237,
  &quot;goldenrod&quot;: #D5A021,
  &quot;alabaster&quot;: #EDE7D9,
  &quot;spanish-gray&quot;: #A49694,
  &quot;dim-gray&quot;: #736B60,
);

// Merge the maps
$theme-colors: map-merge($theme-colors, $custom-colors);</code></pre><p>Notice the <code>map-merge</code> function at the end. &#xA0;That&apos;s what creates our new color classes.</p><p>Now let&apos;s test them in our html (<code>App.vue</code>) file by adding the following:</p><pre><code>&lt;h1&gt;Custom Buttons!&lt;/h1&gt;
&lt;div class=&quot;row&quot;&gt;
  &lt;div class=&quot;col&quot;&gt;
    &lt;button type=&quot;button&quot; class=&quot;btn btn-dark-lava&quot;&gt;Dark Lava&lt;/button&gt;
    &lt;button type=&quot;button&quot; class=&quot;btn btn-goldenrod&quot;&gt;Goldenrod&lt;/button&gt;
    &lt;button type=&quot;button&quot; class=&quot;btn btn-alabaster&quot;&gt;Alabaster&lt;/button&gt;
    &lt;button type=&quot;button&quot; class=&quot;btn btn-spanish-gray&quot;&gt;Spanish Gray&lt;/button&gt;
    &lt;button type=&quot;button&quot; class=&quot;btn btn-dim-gray&quot;&gt;Dim Gray&lt;/button&gt;
  &lt;/div&gt;
&lt;/div&gt;</code></pre><p>And now we check out the site:</p><figure class="kg-card kg-image-card"><img src="https://www.initialapps.com/content/images/2022/06/Screenshot-2022-06-20-11.14.53-PM-1.png" class="kg-image" alt="Customizing bootstrap 5 with Sass! Example provided." loading="lazy" width="1340" height="619" srcset="https://www.initialapps.com/content/images/size/w600/2022/06/Screenshot-2022-06-20-11.14.53-PM-1.png 600w, https://www.initialapps.com/content/images/size/w1000/2022/06/Screenshot-2022-06-20-11.14.53-PM-1.png 1000w, https://www.initialapps.com/content/images/2022/06/Screenshot-2022-06-20-11.14.53-PM-1.png 1340w" sizes="(min-width: 720px) 720px"></figure><p>We now have a set of fully functional customized theme classes!</p><h2 id="accessing-all-of-bootstraps-colors">Accessing all of Bootstrap&apos;s colors</h2><p>If you noticed, bootstrap has a ton of extra <a href="https://getbootstrap.com/docs/5.0/customize/color/">color classes</a>! &#xA0;However, they are not added by default so we can also activate them in our color mapping function.</p><p>In this example, I&apos;ll add the following: <code>purple-100</code>, <code>blue-200</code>, <code>pink</code>, and <code>teal-800</code>.</p><p>We add a bit more html code to our <code>App.vue</code> file:</p><pre><code>&lt;h1&gt;Additional Bootstrap Colors&lt;/h1&gt;
&lt;div class=&quot;row&quot;&gt;
  &lt;div class=&quot;col&quot;&gt;
    &lt;button type=&quot;button&quot; class=&quot;btn btn-purple-100&quot;&gt;Purple-100&lt;/button&gt;
    &lt;button type=&quot;button&quot; class=&quot;btn btn-blue-200&quot;&gt;Blue-200&lt;/button&gt;
    &lt;button type=&quot;button&quot; class=&quot;btn btn-pink&quot;&gt;Pink&lt;/button&gt;
    &lt;button type=&quot;button&quot; class=&quot;btn btn-teal-800&quot;&gt;Teal-800&lt;/button&gt;
  &lt;/div&gt;
 &lt;/div&gt;  </code></pre><p>Also, we append to our <code>main.scss</code> file in the <code>$custom-colors</code>:</p><pre><code>// 4. Include any default map overrides here
// Create your own map
$custom-colors: (
  &quot;dark-lava&quot;: #4B4237,
  &quot;goldenrod&quot;: #D5A021,
  &quot;alabaster&quot;: #EDE7D9,
  &quot;spanish-gray&quot;: #A49694,
  &quot;dim-gray&quot;: #736B60,
  &quot;purple-100&quot;: $purple-100,
  &quot;blue-200&quot;: $blue-200,
  &quot;pink&quot;: $pink,
  &quot;teal-800&quot;: $teal-800,
);

// Merge the maps
$theme-colors: map-merge($theme-colors, $custom-colors);</code></pre><p>Notice, since we use this function after loading the bootstrap variables, we can just add the extra color variables to our custom colors. </p><p>And the final site looks like this:</p><figure class="kg-card kg-image-card"><img src="https://www.initialapps.com/content/images/2022/06/Screenshot-2022-06-21-8.12.14-AM.png" class="kg-image" alt="Customizing bootstrap 5 with Sass! Example provided." loading="lazy" width="1254" height="722" srcset="https://www.initialapps.com/content/images/size/w600/2022/06/Screenshot-2022-06-21-8.12.14-AM.png 600w, https://www.initialapps.com/content/images/size/w1000/2022/06/Screenshot-2022-06-21-8.12.14-AM.png 1000w, https://www.initialapps.com/content/images/2022/06/Screenshot-2022-06-21-8.12.14-AM.png 1254w" sizes="(min-width: 720px) 720px"></figure><h2 id="final-thoughts">Final thoughts</h2><p>Hope this helps you get started setting up Sass on Bootstrap 5!</p><p>Get the source code here: <a href="https://github.com/initialapps/bootstrap-5-sass-example">https://github.com/initialapps/bootstrap-5-sass-example</a></p>]]></content:encoded></item><item><title><![CDATA[Permission denied (publickey) Digital Ocean SSH]]></title><description><![CDATA[How to fix a "Permission Denied (publickey)" error for Digital Ocean, by temporarily allowing password authentication to update your SSH key.]]></description><link>https://www.initialapps.com/permission-denied-publickey-digital-ocean-ssh/</link><guid isPermaLink="false">5eb49ac2ca26f132a97ee4c9</guid><dc:creator><![CDATA[Andrew Akagawa]]></dc:creator><pubDate>Sat, 16 May 2020 15:18:23 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1550527882-b71dea5f8089?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=2000&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1550527882-b71dea5f8089?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=2000&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" alt="Permission denied (publickey) Digital Ocean SSH"><p></p><p>Locked out of your Digital Ocean server? &#xA0;Here&apos;s how to get back in. &#xA0;This short article is for anyone that has lost access to the SSH key used to setup their Digital Ocean droplet. </p><p><strong>This article will not help you, if you do not know the username and password used to setup your server.</strong> &#xA0;<a href="https://www.digitalocean.com/docs/droplets/resources/console/">In that case, you will have to instead reset your root password per the Digital Ocean docs.</a></p><p>Finally, we assume that you have some basic knowledge of Ubuntu, SSH keys, command line editors (if not, just google it).</p><p>Basic overview of the following steps:</p><ul><li>Access the Digital Ocean online terminal</li><li>Update the SSH config file to allow username and password login</li><li>Setup new SSH key</li><li>Revert your SSH config file</li></ul><p></p><h2 id="step-1-access-the-digital-ocean-online-terminal">Step 1: Access the Digital Ocean online terminal</h2><p>Log into your Digital Ocean account, select your droplet, and click Access &gt; Launch Console.</p><figure class="kg-card kg-image-card"><img src="https://s3-us-west-1.amazonaws.com/initialapps/2020/05/access.949745c3cdd9acee5832e28ac3e3ced353e92199d4a0375cf7a380c09aacbdde.png" class="kg-image" alt="Permission denied (publickey) Digital Ocean SSH" loading="lazy"></figure><p>This will get you into your droplet through the online terminal. &#xA0;</p><p>If you&apos;re brave enough, you can try to update your ssh key directly through this terminal, but the interface is a bit clunky and crashed on me. Hence, I recommend the next steps.</p><h2 id="step-2-allow-username-and-password">Step 2: &#xA0;Allow username and password</h2><p>Next, we&apos;ll update the droplet&apos;s config file to allow SSH by username and password. Note, the below will be a less secure setup, but we&apos;ll change it back afterward.</p><p>Using your favorite editor (nano below), open the file:</p><pre><code>sudo nano /etc/ssh/sshd_config</code></pre><p>We&apos;ll update this file to turn off PAM (Pluggable Authentication Modules). &#xA0;Find this line and update to <code>no</code>:</p><pre><code>UsePAM no</code></pre><p>We&apos;ll also update this to allow password authentication:</p><pre><code>PasswordAuthentication yes</code></pre><p>Save and exit the file, and reload SSH on your droplet:</p><pre><code>sudo systemctl restart ssh</code></pre><h2 id="step-3-setup-your-new-ssh-key">Step 3: Setup your new SSH key</h2><p>If you don&apos;t have one already on your local machine, set this up:</p><pre><code>ssh-keygen -t rsa</code></pre><p>Copy your key to the server, using your username and droplet IP address in place of the demo below:</p><pre><code>ssh-copy-id username@droplet_IP</code></pre><p>You should now be able to complete the above, and access your droplet with your original username and password. &#xA0;Note, if you do not know your username and password, you will have to instead <a href="https://www.digitalocean.com/docs/droplets/resources/console/">reset your root password per the Digital Ocean docs</a>.</p><h2 id="step-4-revert-your-ssh-config-file">Step 4: Revert your SSH config file</h2><p>Now that you are setup with SSH keys, you can reset your config file back to its original settings. &#xA0;You can either use the Digital Ocean terminal, or SSH into your droplet <code>ssh username@droplet_IP</code>.</p><p>Using your favorite editor (nano below), open the file:</p><pre><code>sudo nano /etc/ssh/sshd_config</code></pre><p>Revert this file to turn PAM <code>on</code>:</p><pre><code>UsePAM yes</code></pre><p>Revert password authentication to <code>off</code>:</p><pre><code>PasswordAuthentication no</code></pre><p>Save and exit the file, and reload SSH on your droplet:</p><pre><code>sudo systemctl restart ssh</code></pre><p>Your droplet should now be good to go!</p>]]></content:encoded></item><item><title><![CDATA[Tracking where Covid-19 is in Orange County]]></title><description><![CDATA[Easily track and visualize where Corona Virus is spreading in Orange County using data provided by the OC Health Care Agency.]]></description><link>https://www.initialapps.com/orange-county-civic-hacking-for-covid-19/</link><guid isPermaLink="false">5e9c7494ca26f132a97ee3ab</guid><category><![CDATA[Civic Projects]]></category><dc:creator><![CDATA[Andrew Akagawa]]></dc:creator><pubDate>Sun, 19 Apr 2020 18:47:18 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1584118624012-df056829fbd0?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=2000&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1584118624012-df056829fbd0?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=2000&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" alt="Tracking where Covid-19 is in Orange County"><p>We&apos;re excited to be sharing our first <a href="https://www.occiviccoders.com">OC Civic Coder&apos;s</a> project to help easily visualize where Corona Virus is spreading in Orange County using data provided by the <a href="https://www.ochealthinfo.com/phs/about/epidasmt/epi/dip/prevention/novel_coronavirus">OC Health Care Agency</a>. &#xA0;This open source app uses visualization charts, and bubble maps to show where Covid-19 is in Orange County, and how it is spreading over time. &#xA0;</p><p>The goal of this app is to provide tracking data to the general public that&apos;s easy to understand, in efforts to help slow the spread and see if our preventative measures are working. &#xA0;<a href="https://www.occiviccoders.com/covid-response-OC/">This app is hosted on the OC Civic Coders site (check it out)!</a></p><h2 id="how-corona-virus-has-spread-in-orange-county-over-time-by-city">How Corona Virus has spread in Orange County over time by city</h2><!--kg-card-begin: html--><figure class="kg-card kg-image-card"><a href="https://www.occiviccoders.com/covid-response-OC/" target="_blank"><img src="https://s3-us-west-1.amazonaws.com/initialapps/2020/04/OC-covid-spread.png" class="kg-image" alt="Tracking where Covid-19 is in Orange County"></a></figure><!--kg-card-end: html--><p>Case counts have been gradually on the rise from mid March through April, however hospitalizations and ICU counts have been relatively flat. &#xA0;The dropdown menu provides individual case counts by city, thus allowing locals to track and encourage preventative measures close to home. &#xA0;The app additionally breaks down Covid-19 case ratios by gender and age.</p><h2 id="a-bubble-map-for-where-covid-19-is-occurring-in-orange-county">A bubble map for where Covid-19 is occurring in Orange County</h2><!--kg-card-begin: html--><figure class="kg-card kg-image-card"><a href="https://www.occiviccoders.com/covid-response-OC/" target="_blank"><img src="https://s3-us-west-1.amazonaws.com/initialapps/2020/04/OC-covid-location.png" class="kg-image" alt="Tracking where Covid-19 is in Orange County"></a></figure><!--kg-card-end: html--><p>This bubble map displays visual representations for the amount of Corina Virus cases by city in Orange County. &#xA0;The dropdown menu additionally lets you visualize by case counts per population, which in turn displays some interesting results as opposed to visualizing by counts alone. &#xA0;The sidebar to the right sorts case counts from largest to smallest and points out the location when hovered over.</p><p>In addition, the app shows how the spread initially began, trending data by travel related, vs locally spread. &#xA0;Data related to travel is no longer being tracked as most cases now are locally spread within the community.</p><h2 id="built-open-source-by-local-civic-coders">Built open source by local civic coders</h2><p>The Orange County Covid-19 tracker is an open source project made by local residents. &#xA0;It&apos;s an interesting example of coding for causes together to help the local community. &#xA0;The code uses <a href="https://github.com/cheeriojs/cheerio">Cheerio</a>, <a href="https://github.com/axios/axios">Axios</a>, and <a href="https://nodejs.org/en/">Node</a> to get the data from the city website. &#xA0;It then uses <a href="https://www.chartjs.org/">Chartjs</a> and <a href="https://www.mapbox.com/">Mapbox</a> and <a href="https://d3js.org/">D3js</a> to visualize the data. &#xA0;<a href="https://github.com/occiviccoders/covid-response-OC">You can view, download, and contribute to this code on GitHub.</a></p><p></p><p></p><p> &#xA0;</p>]]></content:encoded></item><item><title><![CDATA[OC Civic Coders]]></title><description><![CDATA[Welcome to the very first meetup for OC Civic Coders! Grab your laptops and come out to build something great.]]></description><link>https://www.initialapps.com/oc-civic-coders/</link><guid isPermaLink="false">5e2340f9ca26f132a97ee38f</guid><category><![CDATA[Civic Projects]]></category><dc:creator><![CDATA[Andrew Akagawa]]></dc:creator><pubDate>Sat, 18 Jan 2020 17:32:58 GMT</pubDate><media:content url="https://s3-us-west-1.amazonaws.com/initialapps/2020/01/grayscale-photo-of-computer-laptop-near-white-notebook-and-169573.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://s3-us-west-1.amazonaws.com/initialapps/2020/01/grayscale-photo-of-computer-laptop-near-white-notebook-and-169573.jpg" alt="OC Civic Coders"><p>Welcome to the very first meetup for OC Civic Coders! Grab your laptops and come out to build something great. Feel free to work individually or team up. Connect with others to learn and share knowledge.<br><br>Agenda:<br>1. New project ideas.<br>2. Share your experience levels and what you&apos;d like to gain from the group.<br>3. For those working on a project, share your progress.<br>4. For those completing a project, share what you&apos;ve created!<br>5. Open networking and collaboration time.<br><br>Tips for getting started:<br>- Work on something you&apos;re passionate about.<br>- Work on something practical. For beginners start of with a small project, and simple framework. For others, work on something with a reasonable enough scope to complete it. Look for existing data resources before starting a bunch of code work.<br>- Be prepared to learn. Whether you&apos;re just starting out, or using this as an opportunity to learn a new framework, anything can be achieved with google, patience, and coffee.</p><p>RSVP Here: <a href="https://www.meetup.com/OC-Civic-Coders/events/267756002/">https://www.meetup.com/OC-Civic-Coders/events/267756002/</a></p>]]></content:encoded></item><item><title><![CDATA[Visualizing Traffic Collisions in Los Angeles]]></title><description><![CDATA[Each week there are about a thousand traffic collisions in LA.  This app is a tool to visualize traffic collisions in Los Angeles. ]]></description><link>https://www.initialapps.com/visualizing-traffic-collisions-in-los-angeles/</link><guid isPermaLink="false">5e138ee2ca26f132a97ee349</guid><category><![CDATA[Civic Projects]]></category><dc:creator><![CDATA[Andrew Akagawa]]></dc:creator><pubDate>Mon, 06 Jan 2020 20:13:37 GMT</pubDate><media:content url="https://s3-us-west-1.amazonaws.com/initialapps/2020/01/city-near-mountain-during-golden-hour-2525903.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://s3-us-west-1.amazonaws.com/initialapps/2020/01/city-near-mountain-during-golden-hour-2525903.jpg" alt="Visualizing Traffic Collisions in Los Angeles"><p>Starting 2020 of right with another civic hacking project. &#xA0;This time, we&apos;ve thrown together an app that visualizes traffic collisions in Los Angeles. &#xA0;Each week there are about a thousand traffic collisions in LA. &#xA0;This data visualization tool can hopefully help for deeper insights, which can lead to new solutions. &#xA0;Big thanks to <a href="https://www.initialapps.com/visualizing-traffic-collisions-in-los-angeles/%20lacity.org">lacity.org</a> for providing the data!</p><h3 id="view-the-demo-website-here"><strong>View the demo website here</strong></h3><p><a href="https://developer.initialapps.com/la-collisions/">https://developer.initialapps.com/la-collisions/</a></p><h3 id="view-the-code-on-github"><strong>View the code on GitHub</strong></h3><p><a href="https://github.com/initialapps/la-collisions">https://github.com/initialapps/la-collisions</a></p><h2 id="project-goals"><strong>Project Goals</strong></h2><p>The goals for this project are simple:</p><ul><li>Exercise some JS data processing techniques</li><li>Provide something civically engaging</li><li>Create something open source, and see where others take it</li></ul><p>Feel free to pull the project from <a href="https://github.com/initialapps/la-collisions">GitHub</a>!</p><h2 id="contributing-to-this-project"><strong>Contributing To This Project</strong></h2><p>Civic hacking is an awesome way to learn coding, while providing value to your local community. &#xA0;This pretty close to a full vanilla javascript project, so contributing is easy. &#xA0;</p><h3 id="built-with"><strong>Built With</strong></h3><ul><li><a href="https://www.mapbox.com/" rel="nofollow">Mapbox</a> - For the maps</li><li><a href="https://getbootstrap.com/" rel="nofollow">Bootstrap</a> - Front end framework</li><li><a href="https://jquery.com/" rel="nofollow">jQuery</a> - Who doesn&apos;t use jquery?</li><li><a href="https://www.chartjs.org/" rel="nofollow">chartjs</a> - Nice charts</li><li><a href="https://flatpickr.js.org/" rel="nofollow">flatpickr</a> - Great date range picker</li></ul><h3 id="getting-started"><strong>Getting Started</strong></h3><p>The app is a simple, single page built with <a href="https://www.mapbox.com/" rel="nofollow">Mapbox</a>. Simply grab a Mapbox API key and <a href="https://github.com/initialapps/la-collisions">clone this repo</a> to use.</p><h3 id="connecting-with-contributors"><strong>Connecting With Contributors</strong></h3><p>Please comment below to connect with others. &#xA0;Share what you&apos;d like to see from this project, or share your creations. &#xA0;Let us know what to do next!</p><p><em><em>Happy Coding!</em></em></p>]]></content:encoded></item><item><title><![CDATA[Spotlight on IMC Intertech]]></title><description><![CDATA[IMC Intertech pushes the limits of micro precision parts, with parts less than .012 cu inches, and tolerancing on the level of microns.  ]]></description><link>https://www.initialapps.com/spotlight-imc-intertech/</link><guid isPermaLink="false">5de43a20a6b608073d931f70</guid><category><![CDATA[Websites]]></category><dc:creator><![CDATA[Andrew Akagawa]]></dc:creator><pubDate>Sun, 01 Dec 2019 22:12:11 GMT</pubDate><media:content url="https://s3-us-west-1.amazonaws.com/initialapps/2019/12/imc-micro-parts-atf.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://s3-us-west-1.amazonaws.com/initialapps/2019/12/imc-micro-parts-atf.jpg" alt="Spotlight on IMC Intertech"><p><a href="https://www.imcintertech.com">IMC Intertech</a> is pushing the limits of micro precision parts, with parts less than .012 cu inches, and tolerancing on the level of microns. &#xA0;</p><p>We&apos;re thrilled to be working with <a href="https://www.imcintertech.com">IMC Intertech</a> on the launch of their new website. &#xA0;When it comes to micro precision and extremely small parts, <a href="https://www.imcintertech.com">IMC Intertech</a> provides unparalleled capabilities, offering incredibly small micro-parts, micro-tubes, micro-needles, and micro-sub-assemblies. &#xA0;</p><figure class="kg-card kg-image-card"><img src="https://www.kaizendesk.com/content/images/2019/12/imc-micro-parts-2.png" class="kg-image" alt="Spotlight on IMC Intertech" loading="lazy"></figure><h2 id="imc-intertech-s-unique-capabilities">IMC Intertech&apos;s Unique Capabilities</h2><p><a href="https://www.imcintertech.com">IMC Intertech</a> provides solutions for a very niche specialty of extremely small part fabrication. &#xA0;Our goal was to help showcase that through use of both wide background images and focused part imagery to gain perspective on just how incredible their capabilities are. </p><figure class="kg-card kg-gallery-card kg-width-wide"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://www.kaizendesk.com/content/images/2019/12/micro-fabrication-parts-thumb-2.png" width="1859" height="1328" loading="lazy" alt="Spotlight on IMC Intertech"></div><div class="kg-gallery-image"><img src="https://www.kaizendesk.com/content/images/2019/12/micro-pin-2.jpg" width="3275" height="2339" loading="lazy" alt="Spotlight on IMC Intertech"></div><div class="kg-gallery-image"><img src="https://www.kaizendesk.com/content/images/2019/12/micro-tube-1-1.jpg" width="3228" height="2305" loading="lazy" alt="Spotlight on IMC Intertech"></div></div></div></figure><p>IMC&apos;s capabilities opens new possibilities for designers and innovators, ranging from medical devices, electronics, and many others.</p><h2 id="site-features"><strong><strong>Site Features</strong></strong></h2><p><a href="https://www.imcintertech.com">IMC Intertech</a> is built on WordPress. &#xA0;The site seamlessly integrates with procurement software - <a href="https://www.kaizendesk.com/">KaizenDesk</a>, to streamline your supply chain paperwork, while growing your leads.</p><ul><li>Search engine optimized</li><li>Blog ready</li><li>Mobile optimized</li><li>Fast load times</li><li>Integration with <a href="https://www.kaizendesk.com/">KaizenDesk</a> Online Quoting and Invoicing Platform</li></ul><hr><blockquote><em><em><a href="https://www.initialapps.com/contact-us">Looking to create a new site, or upgrade your current site? Please, don&apos;t hesitate to reach out to us!</a></em></em></blockquote>]]></content:encoded></item><item><title><![CDATA[Testing Out Data-Driven Styling in Mapbox GL JS]]></title><description><![CDATA[Sharing some notes on Mapbox GL JS data driven styling.  Info provided on circle-radius, base, stops, and circle-color.]]></description><link>https://www.initialapps.com/testing-data-driven-styling-mapbox-gl-js/</link><guid isPermaLink="false">5dcb9499a6b608073d931d4d</guid><category><![CDATA[Civic Projects]]></category><dc:creator><![CDATA[Andrew Akagawa]]></dc:creator><pubDate>Fri, 15 Nov 2019 06:44:33 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1473163928189-364b2c4e1135?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1473163928189-364b2c4e1135?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" alt="Testing Out Data-Driven Styling in Mapbox GL JS"><p>Mapbox GL JS is a powerful mapping API, that can create really nice data visualizations and styling. &#xA0;Their maps look really clean and do a nice job at communicating engaging information interactively. &#xA0; </p><p>That said, getting started with Mapbox, and it&apos;s data driven styling may not be completely straightforward, and required me to do a little testing to get started. &#xA0;For this post, I&apos;ll be sharing notes on how this great tool works, using the example Mapbox has provided.</p><p></p><h2 id="style-circles-with-a-data-driven-property">Style circles with a data-driven property</h2><figure class="kg-card kg-image-card"><img src="https://s3-us-west-1.amazonaws.com/initialapps/2019/11/mabpox-gl-js-1.png" class="kg-image" alt="Testing Out Data-Driven Styling in Mapbox GL JS" loading="lazy"></figure><p>Mapbox provides a nice example for how data driven styling works, which maps out ethnicity data in San Francisco. &#xA0;Check out the code by <a href="https://docs.mapbox.com/mapbox-gl-js/example/data-driven-circle-colors/">clicking here</a>, or also pasted below:</p><pre><code class="language-javascript">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;meta charset=&apos;utf-8&apos; /&gt;
    &lt;title&gt;Style circles with a data-driven property&lt;/title&gt;
    &lt;meta name=&apos;viewport&apos; content=&apos;initial-scale=1,maximum-scale=1,user-scalable=no&apos; /&gt;
    &lt;script src=&apos;https://api.tiles.mapbox.com/mapbox-gl-js/v1.5.0/mapbox-gl.js&apos;&gt;&lt;/script&gt;
    &lt;link href=&apos;https://api.tiles.mapbox.com/mapbox-gl-js/v1.5.0/mapbox-gl.css&apos; rel=&apos;stylesheet&apos; /&gt;
    &lt;style&gt;
        body { margin:0; padding:0; }
        #map { position:absolute; top:0; bottom:0; width:100%; }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;

&lt;div id=&apos;map&apos;&gt;&lt;/div&gt;
&lt;script&gt;
mapboxgl.accessToken = &apos;&lt;your access token here&gt;&apos;;
var map = new mapboxgl.Map({
    container: &apos;map&apos;,
    style: &apos;mapbox://styles/mapbox/light-v10&apos;,
    zoom: 12,
    center: [-122.447303, 37.753574]
});

map.on(&apos;load&apos;, function () {

    /* Sample feature from the `examples.8fgz4egr` tileset:
       {
           &quot;type&quot;: &quot;Feature&quot;,
           &quot;properties&quot;: {
               &quot;ethnicity&quot;: &quot;White&quot;
           },
           &quot;geometry&quot;: {
               &quot;type&quot;: &quot;Point&quot;,
               &quot;coordinates&quot;: [ -122.447303, 37.753574 ]
           }
       }
     */
    map.addLayer({
        &apos;id&apos;: &apos;population&apos;,
        &apos;type&apos;: &apos;circle&apos;,
        &apos;source&apos;: {
            type: &apos;vector&apos;,
            url: &apos;mapbox://examples.8fgz4egr&apos;
        },
        &apos;source-layer&apos;: &apos;sf2010&apos;,
        &apos;paint&apos;: {
            // make circles larger as the user zooms from z12 to z22
            &apos;circle-radius&apos;: {
                &apos;base&apos;: 1.75,
                &apos;stops&apos;: [[12, 2], [22, 180]]
            },
            // color circles by ethnicity, using a match expression
            // https://docs.mapbox.com/mapbox-gl-js/style-spec/#expressions-match
            &apos;circle-color&apos;: [
                &apos;match&apos;,
                [&apos;get&apos;, &apos;ethnicity&apos;],
                &apos;White&apos;, &apos;#fbb03b&apos;,
                &apos;Black&apos;, &apos;#223b53&apos;,
                &apos;Hispanic&apos;, &apos;#e55e5e&apos;,
                &apos;Asian&apos;, &apos;#3bb2d0&apos;,
                /* other */ &apos;#ccc&apos;
            ]
        }
    });
});
&lt;/script&gt;

&lt;/body&gt;
&lt;/html&gt;</code></pre><p>If you haven&apos;t already received an access token from Mapbox, you can <a href="https://account.mapbox.com/auth/signup/">signup for one here</a>.</p><p>Mapbox examples are easy to get started, as you can get up and running simply by copying their code into a text file and saving with an html extension (e.g. mapbox-demo.html). &#xA0;You will just need to add your access token, by finding this line in the code, and adding it:</p><pre><code>mapboxgl.accessToken = &apos;&lt;your access token here&gt;&apos;;</code></pre><p></p><h2 id="how-the-styling-works">How the Styling Works</h2><h3 id="circle-radius">Circle Radius</h3><h4 id="stops">Stops</h4><p>By default, this allows you to set radius based on zoom level. &#xA0;The format is: [[z<em>oom</em>, <em>size</em>], [<em>zoom</em>, <em>size</em>]]. &#xA0;</p><p>To demonstrate this, let&apos;s change our stops to be from zoom level 12 to 14, so we can see a more dramatic shift:</p><pre><code class="language-javascript">&apos;circle-radius&apos;: {
	&apos;base&apos;: 1.75,
	&apos;stops&apos;: [[12, 2], [14, 180]]
}</code></pre><p>Now, if you zoom in just a little, here&apos;s the result:</p><figure class="kg-card kg-image-card"><img src="https://s3-us-west-1.amazonaws.com/initialapps/2019/11/mapbox-gl-js-2.png" class="kg-image" alt="Testing Out Data-Driven Styling in Mapbox GL JS" loading="lazy"></figure><p></p><h4 id="base">Base</h4><p>The base is the rate at which the transition takes between stops. &#xA0;It defaults to 1, which is a linear rate. &#xA0;You can go above or below that for an exponential rate in either direction.</p><p>To demonstrate this, lets initiate our map to load at a zoom level exactly in between our stops, at 13:</p><pre><code>var map = new mapboxgl.Map({
    container: &apos;map&apos;,
    style: &apos;mapbox://styles/mapbox/light-v10&apos;,
    zoom: 13,
    center: [-122.447303, 37.753574]
});</code></pre><p>From here, we can try base levels of both 1 and 100, and compare how they look on loading:</p><figure class="kg-card kg-gallery-card kg-width-wide kg-card-hascaption"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://s3-us-west-1.amazonaws.com/initialapps/2019/11/mapbox-gl-js-3.png" width="2398" height="1600" loading="lazy" alt="Testing Out Data-Driven Styling in Mapbox GL JS"></div><div class="kg-gallery-image"><img src="https://s3-us-west-1.amazonaws.com/initialapps/2019/11/mapbox-gj-js-4-1.png" width="2398" height="1600" loading="lazy" alt="Testing Out Data-Driven Styling in Mapbox GL JS"></div></div></div><figcaption>Base 1 (Left), Base 100 (Right)</figcaption></figure><p>Pretty dramatic difference! &#xA0;Setting the base to 1 gives us a linear relationship, so the dot size is half way between the set points on your stops. &#xA0;Moving the base to 100 gives us a more exponential curve, hence the dots are still small when loaded at midpoint, but will sharply increase as you near the stop setting.</p><h3 id="circle-color">Circle Color</h3><p>The circle color property is more straight forward and should just match the color to a given string property in the geojson object. </p><p>The format is: [&apos;match&apos;,[&apos;get&apos;, <em>object key</em>], <em>object value</em>, <em>color</em>,...]</p><p>The given json object has the following values for ethnicity: White, Black, Hispanic, Asian. &#xA0;Let&apos;s try changing the color for &apos;White&apos; to be white:</p><pre><code>&apos;circle-color&apos;: [
    &apos;match&apos;,
    [&apos;get&apos;, &apos;ethnicity&apos;],
    &apos;White&apos;, &apos;#ffffff&apos;, //hex color for white
    &apos;Black&apos;, &apos;#223b53&apos;,
    &apos;Hispanic&apos;, &apos;#e55e5e&apos;,
    &apos;Asian&apos;, &apos;#3bb2d0&apos;,
    /* other */ &apos;#ccc&apos;
]</code></pre><p>Now take a look at our map. &#xA0;It&apos;s a little more difficult to see, but the yellow dots have now changed to white.</p><figure class="kg-card kg-image-card"><img src="https://s3-us-west-1.amazonaws.com/initialapps/2019/11/mapbox-gl-js-5.png" class="kg-image" alt="Testing Out Data-Driven Styling in Mapbox GL JS" loading="lazy"></figure><p></p><h2 id="final-thoughts">Final Thoughts</h2><p>The above post just scratches the surface of what Mapbox can do with data driven styling. &#xA0;You can additionally set sizes based on json properties, add map markers and more. &#xA0;Hope this helps you gain a footing with this API. &#xA0;If you find this info helpful and would like more content about Mapbox or data visualizations, please feel free to comment below.</p>]]></content:encoded></item><item><title><![CDATA[Going Deep on Mongoose Deep Population, and How To Populate Across Multiple Levels]]></title><description><![CDATA[A deep dive into Mongoose populate, to help avoid some pitfalls that you may encounter when setting this up.]]></description><link>https://www.initialapps.com/mongoose-why-you-may-be-having-issues-populating-across-multiple-levels/</link><guid isPermaLink="false">5d745982a6b608073d931af9</guid><category><![CDATA[Tutorials]]></category><dc:creator><![CDATA[Andrew Akagawa]]></dc:creator><pubDate>Wed, 11 Sep 2019 20:02:53 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1489347215392-cee3b4f539aa?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1489347215392-cee3b4f539aa?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" alt="Going Deep on Mongoose Deep Population, and How To Populate Across Multiple Levels"><p>If you&apos;ve made the switch from SQL to NoSQL, you&apos;ve probably started to dabble into Mongoose population as a means to make up for the lack of table joins. &#xA0;This, unfortunately, is one area where NoSQL databases fall short of their SQL counterpart. &#xA0;Mongoose population can be tricky to setup. &#xA0;This article take a deeper dive into Mongoose populate, to help avoid some pitfalls that you may encounter setting this up.</p><h2 id="what-mongoose-populate-does">What Mongoose Populate Does</h2><p>Mongoose stores data sets in JavaScript objects, hence you may have a JS object for <strong>Users</strong>. &#xA0;A user in turn, may have <strong>friends</strong>, which are an array of other <strong>Users</strong>. &#xA0;They will also likely have arrays of other objects, such as <strong>Posts</strong>, <strong>Comments</strong>, and even <strong>Comments</strong> of <strong>Comments</strong>. </p><p>In SQL you could simply perform table joins. &#xA0;For NoSQL databases like Mongo, your best option is to create separate JS object <em>schemas</em> for each, and reference the connection by object ID. &#xA0;For example, the User object will contain arrays for other Users, Posts, Comments, etc. &#xA0;Each array would only contain the object IDs of the objects they are pointing to (i.e. User IDs, Post IDs, Comment IDs, etc). &#xA0;When querying data, you can then call the populate function to load the data of the associated object for the arrays that you select.</p><p>Yes, this has drawbacks. &#xA0;For example, if you delete an object such as a <strong>User</strong>, you will also need to find everywhere that references that user, and delete the user ID from there as well (such as a user&apos;s friends). &#xA0;Not doing so can cause issues when trying to populate the ID&apos;s that no longer exist. &#xA0;Even with these drawbacks, you&apos;ll find that working with the populate function is still probably your best option, over other JavaScript hacks.</p><h2 id="populating-across-multiple-levels">Populating Across Multiple Levels </h2><p>Mongoose provides great <a href="https://mongoosejs.com/docs/populate.html">documentation</a> for how to get started with populating arrays. &#xA0;Below is a simple example they have provided for populating across multiple levels. &#xA0;In this case we have <strong>Users</strong>, with an array of friends that are other <strong>Users</strong>. &#xA0;We would like to populate the object to show the user&apos;s friends, and friends of friends.</p><p>First, we need the <strong>User</strong> schema:</p><pre><code class="language-javascript">const mongoose = require(&apos;mongoose&apos;);
const Schema = mongoose.Schema;
const ObjectId = mongoose.Schema.Types.ObjectId;


const userSchema = new Schema({
      name: String,
      friends: [{ type: ObjectId, ref: &apos;User&apos; }]
});


const User = mongoose.model(&apos;User&apos;, userSchema);</code></pre><p>Notice, we are only tracking an array of Object IDs for the user&apos;s friends. &#xA0;Those Object IDs are just other User IDs.</p><p></p><p>Next, we&apos;ll query by user name, and populate friends, and friends of friends:</p><pre><code class="language-javascript">User.findOne({
    name: &apos;Val&apos;
}).
populate({
    path: &apos;friends&apos;,
    // Get friends of friends - populate the &apos;friends&apos; array for every friend
    populate: { path: &apos;friends&apos; }
}).
exec(function (err, user) {
    if (err) return handleError(err);
    console.log(&apos;Here is the populated user: &apos;, user);
});</code></pre><p>The return value should convert all of the Object IDs in the friends array, to the actual data in the associated <strong>User</strong> schema. &#xA0;In the above example, we&apos;ve done this for 2 levels, so it will also populate data for friends of friends.</p><p></p><h2 id="populating-with-multiple-schemas-across-multiple-levels">Populating With Multiple Schemas Across Multiple Levels</h2><p>In many applications, you&apos;ll want to do a lot more populating. &#xA0;Here&apos;s how to do that. Let&apos;s add posts and post comments to our object.</p><p></p><p>First off, let&apos;s define our Schemas. These will need to be readily available:</p><pre><code class="language-javascript">const mongoose = require(&apos;mongoose&apos;);
const Schema = mongoose.Schema;
const ObjectId = mongoose.Schema.Types.ObjectId;


const userSchema = new Schema({
    name: String,
    friends: [{ type: ObjectId, ref: &apos;User&apos; }],
    posts: [{ type: ObjectId, ref: &apos;Post&apos; }]
});
const postSchema = new Schema({
    name: String,
    content: String,
    comments: [{ type: ObjectId, ref: &apos;Comment&apos; }]
});
const commentSchema = new Schema({
    name: String,
    content: String
});


const User = mongoose.model(&apos;User&apos;, userSchema);
const Post = mongoose.model(&apos;Post&apos;, postSchema);
const Comment = mongoose.model(&apos;Comment&apos;, commentSchema);</code></pre><p></p><p></p><p>Next, we query and populate the document. &#xA0;Let&apos;s say we&apos;re no longer interested in friends of friends, but we want to populate friends, posts, and post comments. &#xA0;Here&apos;s how we populate these:</p><pre><code class="language-javascript">User.findOne({
    name: &apos;Val&apos;
}).
populate({
    path: &apos;friends&apos;,
    model: &apos;User&apos;
}).
populate({
    path: &apos;posts&apos;,
    model: &apos;Post&apos;,
    populate: {
        path: &apos;comments&apos;,
        model: &apos;Comment&apos;
    }
}).
exec(function (err, user) {
    if (err) return handleError(err);
    console.log(&apos;Here is the populated user: &apos;, user);
});</code></pre><p>Notice, how we now have defined the <strong>model </strong>in each object. &#xA0;Mongoose will often need this with multiple schemas. &#xA0;</p><p>To populate multiple paths, we simply repeat the populate function. &#xA0;To dig deeper, we just populate a level deeper within the object. &#xA0;</p><p>Final note, if you get stuck, be sure to log out your errors on execution. &#xA0;Mongoose provides good error handling for those who listen!</p><p></p><p></p><p></p>]]></content:encoded></item></channel></rss>