<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: alex</title>
    <description>The latest articles on DEV Community by alex (@alexalexyang).</description>
    <link>https://dev.to/alexalexyang</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F233377%2F63602ed1-6997-454e-a144-a8a3e57207bc.jpg</url>
      <title>DEV Community: alex</title>
      <link>https://dev.to/alexalexyang</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alexalexyang"/>
    <language>en</language>
    <item>
      <title>MongoDB change streams (web SDK) for real time data tracking</title>
      <dc:creator>alex</dc:creator>
      <pubDate>Sat, 11 Dec 2021 23:22:34 +0000</pubDate>
      <link>https://dev.to/alexalexyang/mongodb-change-streams-4m9m</link>
      <guid>https://dev.to/alexalexyang/mongodb-change-streams-4m9m</guid>
      <description>&lt;p&gt;The real reason for this article is I'm writing down stuff I might refer to in future because I hate the MongoDB docs.&lt;/p&gt;

&lt;p&gt;Skip intro.&lt;/p&gt;

&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;In my on-going journey to become an evil data baron I've come to appreciate the value proposition of villainously tracking people's GPS coordinates in real time.&lt;/p&gt;

&lt;p&gt;The problem is, if we make POST requests to update 10,000 people's coordinates every 30 seconds for 12 hours a day, we'd make already 864,000,000 calls. This is way above most rate limits. Costs would become prohibitive.&lt;/p&gt;

&lt;p&gt;I'm not a top tier filthy rich villain machinating some big name tech mega-conglomerate. I'm just a dev, sitting at my desk, looking at Digital Ocean App Platform $5 droplets.&lt;/p&gt;

&lt;p&gt;Fortunately, we can stream data. I've not calculated the costs in a detailed way but this seems potentially a lot cheaper.&lt;/p&gt;

&lt;p&gt;Streaming is a very different paradigm. After a lot of research I settled on MongoDB change streams. Change streams allows us to monitor data in real time. Note: &lt;strong&gt;only&lt;/strong&gt; monitoring.&lt;/p&gt;

&lt;p&gt;It does not allow us to add or update it in real time. CRUD methods must still be done via regular HTTP calls.&lt;/p&gt;

&lt;p&gt;That really disappointed me. I'd already spent quite some time reading its docs and then integrating it into my app. So now I'm going to rip it out and use an older strategy with a traditional self-hosted back end plus database for my current project. Sad. I really wanted to use a "serverless" DBAAS.&lt;/p&gt;

&lt;p&gt;Nonetheless it's pretty useful. But, the documentation is byzantine. It's a kafkaesque monstrosity that gives me a literal neck ache to look at it. Documentation complexity is a kind of evil in itself.&lt;/p&gt;

&lt;p&gt;So, the real reason for this article is I'm writing down stuff I might refer to in future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;MongoDB Realm&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;I assume for the most part that you're able to do setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set up cluster, database, and/or collection
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Create database and/or collection
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://www.mongodb.com/basics/create-database"&gt;Docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Usually, I'd set up the database and then let my front end generate the collections. But in this case, we need the collection to show up in the Realm app dashboard. So use the MongoDB UX to create it.&lt;/p&gt;

&lt;p&gt;In this example, imagine that I've created a database called "users" and one collection in it also called "users".&lt;/p&gt;

&lt;h4&gt;
  
  
  Give your front end and/or back end client access
&lt;/h4&gt;

&lt;p&gt;Click on "Network Access" in the sidebar of your dashboard and add it. &lt;a href="https://docs.atlas.mongodb.com/security/ip-access-list/#add-ip-access-list-entries"&gt;Docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Do note that if you're connecting from a service like Vercel that has dynamic IP's you'll have to set IP to 0.0.0.0/0. &lt;a href="https://vercel.com/docs/concepts/solutions/databases#allowing-&amp;amp;-blocking-ip-addresses"&gt;Docs&lt;/a&gt;. I don't have an opinion on the security stuff here. I probably should.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup Realm App
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Create Realm App
&lt;/h4&gt;

&lt;p&gt;Go to your MongodDB dashboard, click on the Realm tab, and click on "Create a New App". &lt;a href="https://docs.mongodb.com/realm/manage-apps/create/create-with-realm-ui/"&gt;Docs&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Allow user access
&lt;/h4&gt;

&lt;p&gt;It might be important to note I'm writing mainly from the perspective of a front end client here.&lt;/p&gt;

&lt;p&gt;It was easy to miss this. In order for users to connect, we have to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authenticate users, &lt;a href="https://docs.mongodb.com/realm/authentication/providers/"&gt;docs&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Click on "Authentication" under "Data Access" in the sidebar of Realm dashboard&lt;/li&gt;
&lt;li&gt;Enable the auth type you want and go from there&lt;/li&gt;
&lt;li&gt;Because I don't care about identifying my users, I use &lt;a href="https://docs.mongodb.com/realm/authentication/anonymous/"&gt;Anonymous Authentication&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Set up rules

&lt;ul&gt;
&lt;li&gt;Click on "Rules" under "Data Access" in the sidebar of Realm dashboard&lt;/li&gt;
&lt;li&gt;Set rules for users on your collection&lt;/li&gt;
&lt;li&gt;owner means the user/client who inserts the record, non-owner means anyone else&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Front end
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Basic setup
&lt;/h3&gt;

&lt;p&gt;We'll use the &lt;a href="https://docs.mongodb.com/realm/web/"&gt;Realm Web SDK&lt;/a&gt; with React.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;npm i realm-web&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you're using Anonymous Authentication for users/clients then the only identifier you need to access the Realm app is the Realm app ID. It should be right at the top of your Realm dashboard.&lt;/p&gt;

&lt;p&gt;I've placed all my Realm-related methods in the same module and instantiated a Realm app instance right at the top. All the methods below it have access to this instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import * as Realm from "realm-web";

const realmApp: Realm.App = new Realm.App({ id: realmAppId });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I created a method to auth users/clients. It should be nice to put user into global state but I'm not showing it here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const realmLogin = async () =&amp;gt; {
  const credentials = Realm.Credentials.anonymous();

  try {
    const user: Realm.User = await realmApp.logIn(credentials);

    if (user.id !== realmApp?.currentUser?.id) {
      // Handle auth fail
    }

    return user;
  } catch (err) {
    // Handle auth fail
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can have users auth/log out even though we don't really have a reason to if users are anonymous. Here's how anyway: &lt;code&gt;realmApp?.currentUser?.logOut()&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Set up change stream watcher
&lt;/h2&gt;

&lt;p&gt;I cribbed the following code directly from the Web SDK &lt;a href="https://docs.mongodb.com/realm/web/mongodb/#watch-for-changes"&gt;docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It's important to note that we're using the Web SDK, not the &lt;a href="https://docs.mongodb.com/manual/changeStreams/"&gt;Node one&lt;/a&gt;. They are different. It's easy to start reading the Node docs and not realise for a while that we're reading the wrong docs.&lt;/p&gt;

&lt;p&gt;I prefer the Node change stream patterns more. But, well, we're stuck with this one for now. Anyway, here's a method for watching the stream:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const watchCollection = async () =&amp;gt; {
  // Connect to the users collection in the users database
  const mongodb = realmApp?.currentUser?.mongoClient("mongodb-atlas");
  const users = mongodb?.db("users").collection("users");

  if (!users) {
    return;
  }

  // This is the watcher, note the filter
  const changeStream = users.watch({
    filter: {
      operationType: "insert",
      "fullDocument.banned": false,
    },
  });

  for await (const change of changeStream) {
    switch (change.operationType) {
      case "insert": {
        const { documentKey, fullDocument } = change;
        console.log(`new document with _id: ${documentKey}`, fullDocument);
        break;
      }
      case "update": {
        const { documentKey, fullDocument } = change;
        console.log(`updated document: ${documentKey}`, fullDocument);
        break;
      }
      case "replace": {
        const { documentKey, fullDocument } = change;
        console.log(`replaced document: ${documentKey}`, fullDocument);
        break;
      }
      case "delete": {
        const { documentKey } = change;
        console.log(`deleted document: ${documentKey}`);
        break;
      }
    }
  }

  return changeStream;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://docs.mongodb.com/realm-sdks/js/latest/Realm.MongoDBCollection.html#~ChangeEvent"&gt;List of available change events&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The filter
&lt;/h3&gt;

&lt;p&gt;The cool thing that attracted me to MongoDB Realm's change streams is the filter. I was so excited about it.&lt;/p&gt;

&lt;p&gt;A lot of other databases might allow monitoring in real time, but they don't allow queries in real time on the stream. Change streams allows it.&lt;/p&gt;

&lt;p&gt;I think Apache Kafka's interactive queries do the same thing but it's only available in Java and not in their JavaScript SDK. It's why I picked MongoDB's Realm change streams instead.&lt;/p&gt;

&lt;p&gt;Notice that we're filtering for two criteria:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;filter: {
      operationType: "insert",
      "fullDocument.banned": false,
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because of this, the Realm app will send to the client only records that have been inserted, and where the record field does not contain &lt;code&gt;{"banned": true}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The filters can probably be a lot more complex, involving mathematical operators and the like, just as we could do on regular MongoDB queries. It's just a simple example here.&lt;/p&gt;

&lt;p&gt;Perhaps important to note is that this filter is passed on to the Realm app or kept in cookies or something (I think? Look, I didn't excavate the source code here, ok?) when our front end client connects. So if we're working in a framework that hot reloads like React, we cannot expect changes to the Realm methods to take effect just because our front end app has recompiled. We have to reload the page in the browser as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Get the party started
&lt;/h3&gt;

&lt;p&gt;With the main methods written, all we have to do now is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;realmLogin();
watchCollection();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Functions, HTTPS endpoints, other features
&lt;/h3&gt;

&lt;p&gt;Realms has some other pretty neat features, &lt;a href="https://docs.mongodb.com/realm/functions/"&gt;functions&lt;/a&gt;, &lt;a href="https://docs.mongodb.com/realm/triggers/trigger-types/"&gt;triggers&lt;/a&gt; and &lt;a href="https://docs.mongodb.com/realm/endpoints/"&gt;HTTPS endpoints&lt;/a&gt; being just a few of them.&lt;/p&gt;

&lt;p&gt;Here's one of my functions that I added to "Functions", under "Build", in the Realm sidebar. I named it "deleteAllUserData":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exports = async function(){  
  const userid = context.user.id;

  const userColl = context.services.get("mongodb-atlas").db("users").collection("users");
  const res = await userColl.deleteMany({userid});

  return res;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the name, I call it in my React front end like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user.functions.callFunction("deleteAllUserData")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It deletes all the records created by the current connected user/client instance.&lt;/p&gt;

&lt;h3&gt;
  
  
  End
&lt;/h3&gt;

&lt;p&gt;That's it. Now, get outta here and wreak profit.&lt;/p&gt;

</description>
      <category>javascscript</category>
      <category>mongodb</category>
      <category>streaming</category>
      <category>realtime</category>
    </item>
    <item>
      <title>Checklist to estimate time to make a thing</title>
      <dc:creator>alex</dc:creator>
      <pubDate>Fri, 16 Jul 2021 08:13:26 +0000</pubDate>
      <link>https://dev.to/alexalexyang/how-to-estimate-time-to-make-a-thing-4mep</link>
      <guid>https://dev.to/alexalexyang/how-to-estimate-time-to-make-a-thing-4mep</guid>
      <description>&lt;p&gt;Writing this (mainly for myself) because we keep getting bit in the butt with our estimations.&lt;/p&gt;

&lt;p&gt;This checklist helps interrogate our assumptions. On first glance a task may seem easy but it could hide a ton of other tasks.&lt;/p&gt;

&lt;p&gt;Generally I find quintupling our first estimate brings us a lot closer to the truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Where does it come from?

&lt;ul&gt;
&lt;li&gt;Is it easy to access?&lt;/li&gt;
&lt;li&gt;Do we need authentication?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Does it contain all the properties we need?&lt;/li&gt;
&lt;li&gt;Have we actually seen it or are we just assuming that we probably have this data because it's so obvious? (Don't assume. lol. 😭)&lt;/li&gt;
&lt;li&gt;Does it need to be transformed?

&lt;ul&gt;
&lt;li&gt;Is it complex to transform?&lt;/li&gt;
&lt;li&gt;Does the data's reliability depend on many third parties? E.g., country calling codes 📞 could start with +, 0, 00, or a whole slew of other patterns depending on country. It might take a lot of investigation just to figure out what the data is really about, and how to break it up into component parts suitable for our use case.&lt;/li&gt;
&lt;li&gt;Does the new form need to be stored somewhere?&lt;/li&gt;
&lt;li&gt;Should we work it into a regular ETL and CD process? E.g., daily cron job to ETL public transport traffic data, transform it, test it, then deploy to a server that uses Docker in Kubernetes in Azure 😅.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Do we need to communicate with several parties over a period of time to get this data, and in a form we can use?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  UI/UX (if front end)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What does it look like on mobile and desktop?&lt;/li&gt;
&lt;li&gt;How should the UX behave?&lt;/li&gt;
&lt;li&gt;Are the updated designs readily available?&lt;/li&gt;
&lt;li&gt;How much time to add accessibility?

&lt;ul&gt;
&lt;li&gt;E.g., invisible elements that screen readers should read out, can tab through all elements, UX for focused elements.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Does it work on all browsers we support?&lt;/li&gt;
&lt;li&gt;Do other teams' CSS override ours in production? Could happen when multiple teams develop separately but their work is merged in prod.&lt;/li&gt;
&lt;li&gt;Tests!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reused code/components
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Do we have to reuse code?

&lt;ul&gt;
&lt;li&gt;How easy is it to edit and/or inherit for the new use case?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Is it legacy code?

&lt;ul&gt;
&lt;li&gt;Do we have to update libraries? If yes, do we need regression testing?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Is it complex?

&lt;ul&gt;
&lt;li&gt;E.g., deeply nested modules or 1000+ lines in the module.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Will regression testing be complex? E.g., if edited code touches 10 services, manual testing might be required in spite of extensive test coverage, especially when it's UI/UX.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tests
&lt;/h2&gt;

&lt;p&gt;TESTS. (I'm not great at this. 🤷🏻‍♀️)&lt;/p&gt;

&lt;p&gt;Could add like 30-40% more time but totally worth it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meetings
&lt;/h2&gt;

&lt;p&gt;Some days I code for like 30 min a day because of meetings. But the task in the agile scrum kanban tomato thingamajig backlog says the task I'm working on should take 2 hours. So then I end up spending maybe 4 days on the thing in total, because of meetings and complexities mentioned above. Not even exaggerating.&lt;/p&gt;

&lt;h2&gt;
  
  
  Buffer time
&lt;/h2&gt;

&lt;p&gt;We're human. Procrastination, social obligations, and laundry are real.&lt;/p&gt;

&lt;p&gt;And horror of horrors, equipment breaks down, WiFi fizzles out, botnets attack... 🙀&lt;/p&gt;

&lt;p&gt;Build this in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workflow
&lt;/h2&gt;

&lt;p&gt;All of this assumes that we already have in place a smooth company/department-wide workflow where business/commercial 📊 side snazzily breaks the product up into features, user stories, and/or tasks for easy bite-size dev consumption. Otherwise, that's a whole other thing.&lt;/p&gt;

</description>
      <category>management</category>
    </item>
    <item>
      <title>Invisible content for screen readers with aria-labelledby</title>
      <dc:creator>alex</dc:creator>
      <pubDate>Mon, 12 Jul 2021 16:14:33 +0000</pubDate>
      <link>https://dev.to/alexalexyang/invisible-content-for-screen-readers-with-aria-labelledby-19f6</link>
      <guid>https://dev.to/alexalexyang/invisible-content-for-screen-readers-with-aria-labelledby-19f6</guid>
      <description>&lt;p&gt;This is more a note for myself than anybody else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;What if you have a group like the following but for some reason you're not allowed to have a title to tell people what it's for? Maybe your designers think it's contextual enough for sighted people or something.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div role="radiogroup"&amp;gt;
  &amp;lt;input type="radio" id="fish" name="fish"&amp;gt;
  &amp;lt;label for="fish"&amp;gt;🐟&amp;lt;/label&amp;gt;

  &amp;lt;input type="radio" id="mammal" name="mammal"&amp;gt;
  &amp;lt;label for="mammal"&amp;gt;🧸&amp;lt;/label&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;WebAIM has really good tips on how to hide content from sighted users. I want to focus only on what they say about &lt;code&gt;display: none&lt;/code&gt; and &lt;code&gt;visibility: hidden&lt;/code&gt; &lt;a href="https://webaim.org/techniques/css/invisiblecontent"&gt;here&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
These styles will hide content from all users. The content is removed from the visual flow of the page and is ignored by screen readers. &lt;b&gt;Do not use this CSS if you want the content to be read by a screen reader. But DO use it for content you want hidden from all users.&lt;b&gt;
&lt;/b&gt;&lt;/b&gt;
&lt;/blockquote&gt;

&lt;p&gt;True. However, &lt;a href="https://www.accessibility-developer-guide.com/examples/sensible-aria-usage/label-labelledby/#referencing-a-label"&gt;Accessibility Developer Guide&lt;/a&gt; says:&lt;/p&gt;

&lt;blockquote&gt;
elements hidden using CSS can still be referenced
&lt;/blockquote&gt;

&lt;p&gt;How? With &lt;code&gt;aria-labelledby&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Let do this with our &lt;code&gt;radiogroup&lt;/code&gt; example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div role="radiogroup" aria-labelledby="hidden-content"&amp;gt;

  // Exciting new span!
  &amp;lt;span id="hidden-content"&amp;gt;Which animal family do you like better?&amp;lt;/span&amp;gt;

  &amp;lt;input type="radio" id="fish" name="fish"&amp;gt;
  &amp;lt;label for="fish"&amp;gt;🐟&amp;lt;/label&amp;gt;

  &amp;lt;input type="radio" id="mammal" name="mammal"&amp;gt;
  &amp;lt;label for="mammal"&amp;gt;🧸&amp;lt;/label&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#hidden-content {
  display: none;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, the span is hidden from sighted users but can be read out by screen-readers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not &lt;code&gt;aria-label&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;We might ask, why not just apply &lt;code&gt;aria-label&lt;/code&gt; to the wrapper &lt;code&gt;div&lt;/code&gt;? &lt;a href="https://www.accessibility-developer-guide.com/examples/sensible-aria-usage/label-labelledby/#text-not-searchable"&gt;ADG&lt;/a&gt; says: "Text added with aria-label is not searchable in browsers."&lt;/p&gt;

&lt;h2&gt;
  
  
  The end
&lt;/h2&gt;

&lt;p&gt;🙂&lt;/p&gt;

</description>
      <category>a11y</category>
    </item>
    <item>
      <title>Empty emails from specific address from gmail trash</title>
      <dc:creator>alex</dc:creator>
      <pubDate>Mon, 15 Jun 2020 01:45:23 +0000</pubDate>
      <link>https://dev.to/alexalexyang/empty-emails-from-specific-address-from-gmail-trash-33kf</link>
      <guid>https://dev.to/alexalexyang/empty-emails-from-specific-address-from-gmail-trash-33kf</guid>
      <description>&lt;h1&gt;
  
  
  Problem
&lt;/h1&gt;

&lt;p&gt;One of my clients uses Gmail and their abuser keeps emailing them. They read them and get affected.&lt;/p&gt;

&lt;p&gt;Gmail allows us to filter incoming emails to spam or trash. They've done that. But they go and read them in trash (emotions are hard).&lt;/p&gt;

&lt;p&gt;Gmail doesn't have a filter to let us empty emails from specific senders from trash.&lt;/p&gt;

&lt;h1&gt;
  
  
  Solution
&lt;/h1&gt;

&lt;p&gt;A Google script that automatically empties abuser's email from Gmail trash.&lt;/p&gt;

&lt;h1&gt;
  
  
  Notes
&lt;/h1&gt;

&lt;p&gt;Skip to end of article for full script if that's all you need.&lt;/p&gt;

&lt;p&gt;This script only empties emails from specific sender from trash. It does not filter incoming ones to trash. To do that, use Gmail's &lt;a href="https://support.google.com/mail/answer/6579?hl=en"&gt;filters&lt;/a&gt;. Remember to set "never send to spam" so the emails always go to trash.&lt;/p&gt;

&lt;p&gt;Some have pointed out that emails might be evidence. Think about that before using this script. I cobbled this script together because we're prioritising mental health for now.&lt;/p&gt;

&lt;h1&gt;
  
  
  APIs
&lt;/h1&gt;

&lt;p&gt;This solution uses two main APIs. I don't know their exact names but:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developers.google.com/gmail/api/v1/reference/users/threads"&gt;Gmail&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developers.google.com/apps-script/reference/gmail/gmail-app"&gt;GmailApp (Apps-Script)&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Code walkthrough
&lt;/h1&gt;

&lt;p&gt;The script has to run from the client's Google account. We can test it on our own first.&lt;/p&gt;

&lt;p&gt;First, log into our Google account. Then go to &lt;a href="https://script.google.com"&gt;script.google.com&lt;/a&gt;. This is where we type in the following code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some prep
&lt;/h2&gt;

&lt;p&gt;Take a glance at the preparatory stuff before I explain them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const clientGmail = 'myClient@This-Has-To-Be-Gmail-Here.com'

const page = {
    q: "in:trash from:abuser@Whatever-Their-Email-Is.com",
    pageToken: null,
    }

const limit = 5400000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Client's email in &lt;code&gt;const clientGmail&lt;/code&gt;. Note: the script has to run from the client's Google account, so this has to be their Gmail address. However, because we're testing this on our own accounts first, set this to our own Gmail address.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;page&lt;/code&gt; object contains details in its &lt;code&gt;q&lt;/code&gt; property that allows Gmail to search for the abuser's email in gmail trash.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;const limit&lt;/code&gt; is the time period from which we want to delete the abuser's email. I've set it to 1.5 hours in milliseconds. This means the script deletes any email from the abuser that came in within the last 1.5 hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  The function itself
&lt;/h2&gt;

&lt;p&gt;Take a brief look and I'll explain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function deleteFromTrash() {
  const threadList = Gmail.Users.Threads.list('me', page);

  threadList.threads.forEach(thread =&amp;gt; {
  const id = thread.id
  const threadByOtherAPI = GmailApp.getThreadById(id)

  const threadDate = threadByOtherAPI.getLastMessageDate()
  const timeNow = new Date
  const period = timeNow - threadDate
  const recent = period &amp;lt; limit

  if (recent) {
    Gmail.Users.Threads.remove(clientGmail, id);
    }
  })
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;threadList&lt;/code&gt; uses the Gmail API to find all the threads that fit the description in &lt;code&gt;page.q&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const threadList = Gmail.Users.Threads.list('me', page);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To clarify: &lt;code&gt;threadList&lt;/code&gt; contains multiple threads. A thread contains one or more emails.&lt;/p&gt;

&lt;p&gt;We then loop through &lt;code&gt;threadList&lt;/code&gt; with a &lt;code&gt;forEach&lt;/code&gt; loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;threadList.threads.forEach(thread =&amp;gt; {
    ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As far as I know, the Gmail API doesn't let us access the thread itself, but it gives us its ID. So, during each loop, we use the &lt;code&gt;GmailApp&lt;/code&gt; (Apps-Script) API to get the thread by its ID. Like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const id = thread.id
const threadByOtherAPI = GmailApp.getThreadById(id)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we get the date of the latest email in the thread, and check if it's within our time period. We do this because emails from long ago are of sentimental or other value to our client, so we don't want to delete them.&lt;/p&gt;

&lt;p&gt;Here's how we check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const threadDate = threadByOtherAPI.getLastMessageDate()
const timeNow = new Date
const period = timeNow - threadDate
const recent = period &amp;lt; limit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, if the email is definitely &lt;code&gt;recent&lt;/code&gt;, we empty it from trash using the Gmail API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (recent) {
  Gmail.Users.Threads.remove(clientGmail, id);
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Turn on Gmail in Google Services
&lt;/h1&gt;

&lt;p&gt;The script won't work just yet. In script.google.com where we've just written the script:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to "Resources" in the menu&lt;/li&gt;
&lt;li&gt;Click on "Advanced Google services"&lt;/li&gt;
&lt;li&gt;Scroll down to Gmail API and turn it on&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Set project trigger
&lt;/h1&gt;

&lt;p&gt;Final step here. From script.google.com:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to "Edit" in the menu&lt;/li&gt;
&lt;li&gt;Click on "Current project's triggers"&lt;/li&gt;
&lt;li&gt;In the new window that opens up, click the "Add Trigger" button in the lower right of the screen&lt;/li&gt;
&lt;li&gt;Set the interval during which the script should run. Note that we've set &lt;code&gt;const limit&lt;/code&gt; to 1.5 hours. My own interval is "every hour". So the script runs every hour and checks if any abusers from the abuser arrived in the last 1.5 hours.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last point bears some thinking. I will do this in the next section.&lt;/p&gt;

&lt;h1&gt;
  
  
  Rate limits
&lt;/h1&gt;

&lt;p&gt;Rate limits for scripts and their triggers can be found &lt;a href="https://developers.google.com/apps-script/guides/services/quotas"&gt;here&lt;/a&gt;. The problem is, it's not clear to me if we should be looking at "Current quotas" or "Current limitations".&lt;/p&gt;

&lt;p&gt;I also don't know what the difference is between Consumer, G Suite Basic, and G Suite free edition. Is our regular gmail account the first, second, or third? No idea.&lt;/p&gt;

&lt;p&gt;Anyway, I've far exceeded trigger limits in both tables by now. So maybe they don't matter? I have no flippin' clue.&lt;/p&gt;

&lt;p&gt;But it might matter to you, for whatever reason. So, keep these limits in mind.&lt;/p&gt;

&lt;h1&gt;
  
  
  Full script
&lt;/h1&gt;

&lt;p&gt;Remember to run this script and enable Gmail in Google Services on client's account.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const clientGmail = 'myClient@This-Has-To-Be-Gmail-Here.com'

const page = {
    q: "in:trash from:abuser@Whatever-Their-Email-Is.com",
    pageToken: null,
    }

const limit = 5400000

function deleteFromTrash() {
  let threadList = Gmail.Users.Threads.list('me', page);

  threadList.threads.forEach(thread =&amp;gt; {
  const id = thread.id
  const threadByOtherAPI = GmailApp.getThreadById(id)

  const threadDate = threadByOtherAPI.getLastMessageDate()
  const timeNow = new Date
  const period = timeNow - threadDate
  const recent = period &amp;lt; limit

  if (recent) {
    Gmail.Users.Threads.remove(clientGmail, id);
    }
  })
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>javascript</category>
      <category>googlecloud</category>
    </item>
    <item>
      <title>Basic space setup and CRUD with Contentful CLI for development</title>
      <dc:creator>alex</dc:creator>
      <pubDate>Sat, 08 Feb 2020 17:18:17 +0000</pubDate>
      <link>https://dev.to/alexalexyang/basic-contentful-cli-commands-and-crud-2kg1</link>
      <guid>https://dev.to/alexalexyang/basic-contentful-cli-commands-and-crud-2kg1</guid>
      <description>&lt;p&gt;We love CLI's. They help us automate things. But Contentful's CLI documentation is scattered all over the place. Here, I walk you through the basic bits needed for development. This is not comprehensive, but it should help get you going.&lt;/p&gt;

&lt;h1&gt;
  
  
  Basic setup
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Get the CLI
&lt;/h2&gt;

&lt;p&gt;Brew, yarn, or npm. Eg: &lt;code&gt;npm install -g contentful-cli&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Ref: &lt;a href="https://github.com/contentful/contentful-cli"&gt;Contentful CLI git repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get your CMA
&lt;/h2&gt;

&lt;p&gt;Log into Contentful. Go to Settings. Go to Content management tokens. Click on "Generate personal token". Copy the token somewhere (important because you'll never see it on the site ever again).&lt;/p&gt;

&lt;h2&gt;
  
  
  Login on CLI
&lt;/h2&gt;

&lt;p&gt;Run this in shell: &lt;code&gt;contentful login --management-token YOUR_CONTENT_MANAGEMENT_TOKEN&lt;/code&gt;. It'll tell you it saved your access token in .contentfulrc.json in your present working directory.&lt;/p&gt;

&lt;p&gt;Ref: &lt;a href="https://github.com/contentful/contentful-cli/tree/master/docs/login"&gt;Contentful CLI - Documentation&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a space
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;contentful space create --name 'Your Space Name'&lt;/code&gt;. You might need &lt;code&gt;--management-token&lt;/code&gt; and pass in your content management key. And &lt;code&gt;--default-locale&lt;/code&gt; if you want to set a default locale that isn't us-En.&lt;/p&gt;

&lt;p&gt;If successful, it'll show you your space name alongside your space-id. Copy the space-id down somewhere.&lt;/p&gt;

&lt;p&gt;Ref: &lt;a href="https://github.com/contentful/contentful-cli/tree/master/docs/space"&gt;Contentful CLI - Space command&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Remember your space
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;contentful space use -s YOUR_SPACE_ID&lt;/code&gt;. This lets the CLI remember which space you're working in so you don't have to pass in &lt;code&gt;--space-id&lt;/code&gt; all the time.&lt;/p&gt;

&lt;p&gt;Ref: &lt;a href="https://github.com/contentful/contentful-cli/tree/master/docs/space/use"&gt;Contentful CLI - &lt;code&gt;space use&lt;/code&gt; command&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Logout
&lt;/h2&gt;

&lt;p&gt;Don't logout now. But if you want to: &lt;code&gt;contentful logout&lt;/code&gt;. It'll delete the .contentfulrc.json.&lt;/p&gt;

&lt;h1&gt;
  
  
  Set up space with migrations
&lt;/h1&gt;

&lt;p&gt;Now it's time to create content types (like database tables) and their fields in your space. The CLI uses a migration script written in JavaScript to do this. It's not immediately clear from the docs if we can write this in other languages and I don't have time to check right now.&lt;/p&gt;

&lt;p&gt;In general, most of the info is here: &lt;a href="https://github.com/contentful/contentful-migration"&gt;contentful-migration - content model migration tool&lt;/a&gt;. But this is a large chunk of everything.&lt;/p&gt;

&lt;p&gt;You'll need to look at the data model too, to figure out what you can use to make, say, a blog post title and a post body: &lt;a href="https://www.contentful.com/developers/docs/concepts/data-model/"&gt;Contentful data model&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is a more specific way to set things up on a basic level: &lt;a href="https://www.contentful.com/blog/2017/09/18/using-the-contentful-migration-cli/"&gt;The right way to migrate your content using the Contentful Migration CLI&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In brief, your script should look something like this, taken directly from the link above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module.exports = function(migration) {
  const author = migration.createContentType('author')
  author
    .name('Author')
    .description('Author of a blog post')

  author
    .createField('fullName')
    .name('Full name')
    .type('Symbol')
    .required(true)

  author
    .createField('twitter')
    .name('Twitter')
    .type('Symbol')
    .validations([
      { "unique": true },
      { "regexp": 
        { "pattern": "^\\w[\\w.-]*@([\\w-]+\\.)+[\\w-]+$" }
      }
    ])

  const blogPost = migration.editContentType('blogPost')
  blogPost.createField('author')
    .name('Author')
    .type('Link')
    .linkType('Entry')
    .validations([
      { "linkContentType": ["author"] }
    ])
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put it in migrations/01-add-author.js and run &lt;code&gt;contentful space migration migrations/01-add-author.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you didn't run &lt;code&gt;contentful space use -s YOUR_SPACE_ID&lt;/code&gt; earlier, you might have to pass in &lt;code&gt;--space-id YOUR_SPACE_ID&lt;/code&gt; too.&lt;/p&gt;

&lt;p&gt;I'll paste my whole blog setup migration script at the end of this article so you can have something a little meatier to look at.&lt;/p&gt;

&lt;h1&gt;
  
  
  Delete content types and fields
&lt;/h1&gt;

&lt;p&gt;This is our development phase so we CRUD everything all the time. We've created content types. But how to delete? It's probably possible to use native commands to delete stuff but, again, it's not immediately clear from the docs how to do this.&lt;/p&gt;

&lt;p&gt;I'm in a rush and I'm too tired from reading docs all day so I'll cheat a little and use this library called &lt;a href="https://github.com/jugglingthebits/contentful-clean-space"&gt;contentful-clean-space&lt;/a&gt;. Install it: &lt;code&gt;npm install -g contentful-clean-space&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then, run: &lt;code&gt;contentful-clean-space --space-id YOUR_SPACE_ID --accesstoken YOUR_CONTENT_MANAGEMENT_TOKEN --content-types&lt;/code&gt;. This deletes all entries/records and content types too. Check out their repo for a bit more info.&lt;/p&gt;

&lt;p&gt;(Reads of entries are through the Contentful &lt;a href="https://www.contentful.com/developers/docs/references/content-delivery-api/"&gt;CDA&lt;/a&gt; and updates are done later through the &lt;a href="https://www.contentful.com/developers/docs/references/content-management-api/"&gt;CMA&lt;/a&gt;)&lt;/p&gt;

&lt;h1&gt;
  
  
  The end
&lt;/h1&gt;

&lt;p&gt;I think that's it.&lt;/p&gt;

&lt;h1&gt;
  
  
  References
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://github.com/contentful/contentful-cli/tree/master/docs"&gt;CLI docs&lt;/a&gt;: basic use of CLI like login and how to do a migration. But does not include what a migration file looks like, and how to add content types and fields to them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/contentful/contentful-migration"&gt;CLI migration docs&lt;/a&gt;: details on how to write the migration script.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.contentful.com/developers/docs/concepts/data-model"&gt;Data model&lt;/a&gt;: tells you about what fields can be added to your migration script, but has no info on what the script itself looks like.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.robinandeer.com/blog/2019/04/26/automated-contentful-migrations/"&gt;Automated Contentful migrations&lt;/a&gt;: a couple of examples for migration scripts worth skimming through for a start.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.contentful.com/blog/2017/09/18/using-the-contentful-migration-cli/"&gt;The right way to migrate your content using the Contentful Migration CLI&lt;/a&gt;: more useful details for migration scripts, like how to link from one content type to another:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const blogPost = migration.editContentType('blogPost')
  blogPost.createField('author')
    .name('Author')
    .type('Link')
    .linkType('Entry')

    // Isolates link to only the "author" content type.
    .validations([
      { "linkContentType": ["author"] }
    ])
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.contentfulcommunity.com/t/confusing-validations-error-from-the-migrations-cli/776/4"&gt;How to do validations on items({})&lt;/a&gt;. Basically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;blogPost.createField("categories")
    .name("Categories")
    .required(false)
    .type('Array')
    .items({
        type: 'Link',
        linkType: "Entry",

        // Right here.
        validations: [{
            linkContentType: [
                "categories"
            ],
        }]

    })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.cheatography.com/gburgett/cheat-sheets/contentful-migration/"&gt;Contentful Migration Cheat Sheet&lt;/a&gt;: clues you in on how to do some of these fields, and also nice reminders.&lt;/p&gt;

&lt;p&gt;Delete content types and fields from CLI: &lt;a href="https://github.com/jugglingthebits/contentful-clean-space"&gt;contentful-clean-space&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  My blog site migration script
&lt;/h1&gt;

&lt;p&gt;It's not the best but it's a good start. It sets up content types and fields for generic pages, blog posts, and default site settings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module.exports = (migration, context) =&amp;gt; {

    // BLOG POST CONTENT TYPE

    const blogPost = migration.createContentType("blogPost")
        .name("Blog Post")
        .description("Blog post model")
        .displayField("title")

    blogPost.createField("title")
        .name("Title")
        .type("Symbol")
        .required(false)

    blogPost.createField("body")
        .name("Body")
        .type("Text")
        .required(false)

    blogPost.createField("author")
        .name("Author name")
        .type("Symbol")
        .required(false)

    blogPost.createField("datetime")
        .name("Datetime")
        .type("Date")
        .required(false)

    blogPost.createField("categories")
        .name("Categories")
        .type('Array')
        .items({
            type: 'Link',
            linkType: "Entry",
            validations: [{
                linkContentType: [
                    "categories"
                ],
            }]
        })
        .required(false)

    blogPost.createField("tags")
        .name("Tags")
        .type("Array")
        .items({ "type": "Symbol" })


    blogPost.createField("featuredImage")
        .name("Featured image")
        .type("Link")
        .linkType("Asset")
        .required(false)


    // CATEGORIES CONTENT TYPE

    const categories = migration.createContentType('categories')
        .name('Categories')
        .description('Categories for blog posts')
        .displayField("category")

    categories.createField('category')
        .name('Category')
        .type('Symbol')
        .required(true)

    categories.createField('slug')
        .name('URL Slug')
        .type('Symbol')
        .validations([{ "unique": true }])
        .required(false)

    categories.createField('featuredImage')
        .name('Featured image')
        .type('Link')
        .linkType('Asset')
        .required(false)

    categories.createField('description')
        .name('Description')
        .type('Text')
        .required(false)


    // PAGE CONTENT TYPE

    const page = migration.createContentType("page")
        .name("Page")
        .description("Page model")
        .displayField("title")

    page.createField("title")
        .name("Title")
        .type("Symbol")
        .required(false)

    page.createField("body")
        .name("Body")
        .type("Text")
        .required(false)

    page.createField("featuredImage")
        .name("Featured image")
        .type("Link")
        .linkType("Asset")
        .required(false)

    // SITE SETTINGS

    const siteSettings = migration.createContentType("siteSettings")
        .name("Site settings")
        .description("Site Settings model")
        .displayField("siteName")

    siteSettings.createField("siteName")
        .name("Site name")
        .type("Symbol")
        .required(false)

    siteSettings.createField("author")
        .name("Author")
        .type("Symbol")
        .required(false)

    siteSettings.createField("address")
        .name("Address")
        .type("Symbol")
        .required(false)

    siteSettings.createField("phoneNumber")
        .name("Phone number")
        .type("Symbol")
        .required(false)

    siteSettings.createField("email")
        .name("Email")
        .type("Symbol")
        .required(false)

    siteSettings.createField("facebookLink")
        .name("Facebook link")
        .type("Symbol")
        .required(false)

    siteSettings.createField("twitterLink")
        .name("Twitter link")
        .type("Symbol")
        .required(false)

    siteSettings.createField("instagramLink")
        .name("Instagram link")
        .type("Symbol")
        .required(false)

    siteSettings.createField("defaultImage")
        .name("Default Image")
        .type("Link")
        .linkType("Asset")
        .required(false)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>contentful</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to make URL path prefix for section of website in Gatsby</title>
      <dc:creator>alex</dc:creator>
      <pubDate>Fri, 10 Jan 2020 12:24:51 +0000</pubDate>
      <link>https://dev.to/alexalexyang/how-to-make-url-path-prefix-for-section-of-website-in-gatsby-3cb4</link>
      <guid>https://dev.to/alexalexyang/how-to-make-url-path-prefix-for-section-of-website-in-gatsby-3cb4</guid>
      <description>&lt;p&gt;Let's say we want all our Gatsby blog posts to have a /blog/ path prefix. Like: example.com/blog/20202-01-10-i-went-to-mars.&lt;/p&gt;

&lt;p&gt;It can be any other section but let's go with blog for this article.&lt;/p&gt;

&lt;p&gt;Most tutorials say to put the blog posts in src/pages/blog. The post URLs will automatically come out the way we want. However, that assumes we write each blog post as a JSX component and with its own graphql query if there's markdown involved. What if we use a page template for the blog posts? It won't work anymore. Didn't for me. &lt;em&gt;(Update 24 Feb 2020: it does work, I was being stupid somehow back then, I think. But this article presents another valid way to do it anyway.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Instead of doing that, let's put blogpostTemplate.js in src/templates/blogpostTemplate and each blog post in src/blogposts/individual_post_dir/post.md. Like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/templates/
└── blogpostTemplate.js

src/blogposts
└── 2020-01-01-individual-post-dir
    ├── 2020-01-01-first-post.md
    └── shoe-800x600.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I won't discuss how to make a page template. It's easy to find resources on that. Anyway, presumably you're here because you've already made a template and found path prefixes broken. I'll focus only on creating the path prefix.&lt;/p&gt;

&lt;h1&gt;
  
  
  Usual page template setup in gatsby-node.js
&lt;/h1&gt;

&lt;p&gt;Now, open up gatsby-node.js in the project root directory. If you've already created a page template for the blog (or whatever other section of your site), the following code will be familiar to you.&lt;/p&gt;

&lt;p&gt;There are two or three ways to do it. Some use async/await, some use &lt;code&gt;then&lt;/code&gt;. I feel it's more a matter of aesthetic taste than anything else. I prefer async/await but for goodness only knows what reason I went with &lt;code&gt;then&lt;/code&gt; for this project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const path = require("path")
const { createFilePath } = require(`gatsby-source-filesystem`)

exports.createPages = ({ actions, graphql }) =&amp;gt; {
  const { createPage } = actions

  return graphql(`

      blogposts: allMarkdownRemark(
        filter: { fileAbsolutePath: { glob: "**/src/blogposts/*/*.md" } }
      ) {
        edges {
          node {
            html
            frontmatter {
              path
            }
            fields {   // Take note of this part
              slug
            }
          }
        }
      }
    }
  `).then(res =&amp;gt; {
    if (res.errors) {
      return Promise.reject(res.errors)
    }

    res.data.blogposts.edges.forEach(({ node }) =&amp;gt; {
        createPage({
          path: node.fields.slug,  // Look, it's here again
          component: path.resolve("src/templates/blogpostTemplate.js"),
          context: {
            slug: node.fields.slug,  // We'll create it in a bit
          },
        })
      })

  })
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main portions in the code above are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;const { createPage } = actions&lt;/code&gt;: destructure &lt;code&gt;createPage&lt;/code&gt; from &lt;code&gt;actions&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;return an allMarkdownRemark graphql query named &lt;code&gt;blogposts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;use &lt;code&gt;createPage&lt;/code&gt; and the results in &lt;code&gt;res.data.blogposts.edges.forEach()&lt;/code&gt; to create pages for each individual blog post using blogpostTemplate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All pretty standard as a hundred and one tutorials out there will tell you. &lt;code&gt;fields { slug }&lt;/code&gt; is important. It doesn't make sense now. But we'll make it in the next section. Gatsby will use this to create the path prefix.&lt;/p&gt;

&lt;h1&gt;
  
  
  The path prefix
&lt;/h1&gt;

&lt;p&gt;Next, the exciting part. We create the path prefix. Right below the above code in gatsby-node.js, we paste in this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exports.onCreateNode = ({ node, getNode, actions }) =&amp;gt; {
  const { createNodeField } = actions
  if (
    node.internal.type === `MarkdownRemark` &amp;amp;&amp;amp;
    node.fileAbsolutePath.includes("blogposts")
  ) {
    const slug = createFilePath({ node, getNode, basePath: `src/blogposts` })
    createNodeField({
      node,
      name: `slug`,
      value: `/blog${slug}`, // Here we are, the path prefix
    })
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whenever we run &lt;code&gt;Gatsby develop&lt;/code&gt; or &lt;code&gt;build&lt;/code&gt;, Gatsby creates nodes for all our directories and markdown files. It then uses these nodes for routing and linking and whatever.&lt;/p&gt;

&lt;p&gt;As the name of the function above suggests, &lt;code&gt;onCreateNode&lt;/code&gt; does something upon the creation of a node. We can do whatever we want with it. For our purposes, we have an if statement that checks if the file is markdown, and if it's contained in the blogposts directory.&lt;/p&gt;

&lt;p&gt;If it is, then we use &lt;code&gt;createFilePath&lt;/code&gt; to create a slug. The slug is just the blog post directory concatenated with the blog post filename.&lt;/p&gt;

&lt;p&gt;To add the /blog/ path prefix, add it to value in &lt;code&gt;createNodeField&lt;/code&gt; as shown above: &lt;code&gt;/blog${slug}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Gatsby will now use this value to create the paths to all our blogposts.&lt;/p&gt;

&lt;p&gt;Following the example in the directory tree above, our post will be at example.com/blog/2020-01-01-individual-post-dir/2020-01-01-first-post.&lt;/p&gt;

&lt;h1&gt;
  
  
  Adapting the blog post template
&lt;/h1&gt;

&lt;p&gt;Remember to edit blogpostTemplate.js. Tutorials usually tell us to have this query or very similar in it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const query = graphql`
  query($path: String!) {
    markdownRemark(frontmatter: { path: { eq: $path } }) {
      html
      frontmatter {
        title
        ...
      }
    }
  }
`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks for a &lt;code&gt;path&lt;/code&gt; tag in the markdown frontmatter. However, we don't have &lt;code&gt;path&lt;/code&gt; anymore. We have &lt;code&gt;slug&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you're already using &lt;code&gt;slug&lt;/code&gt;, lucky you. If you aren't, change it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const query = graphql`
  query($slug: String!) {
    markdownRemark(fields: { slug: { eq: $slug } }) {
      ...
    }
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  The end
&lt;/h1&gt;

&lt;p&gt;For some reason, it's been frightfully difficult to find resources on how to do this. The best resource so far is actually the official Gatsby tutorial &lt;a href="https://www.gatsbyjs.org/tutorial/part-seven/"&gt;part 7&lt;/a&gt;. I think it's worthwhile to at least skim it for a better sense of things. In fact, I'm sure if people went through it, they'd figure things out faster than I did. But, I think, because the tutorial doesn't explicitly talk about prefixes, most people skip it.&lt;/p&gt;

&lt;p&gt;A whole lot of people are asking about this online and trying all kinds of funny code acrobatics so I thought to write this down.&lt;/p&gt;

&lt;p&gt;I may have misconceptions about how things are done in Gatsby. Or I've got things wrong. Or maybe you have a quicker, or more efficient way of doing this. Feel free to tell me these things.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to use markdown in pages in Gatsby</title>
      <dc:creator>alex</dc:creator>
      <pubDate>Mon, 07 Oct 2019 07:54:00 +0000</pubDate>
      <link>https://dev.to/alexalexyang/how-to-use-markdown-in-pages-in-gatsby-5dee</link>
      <guid>https://dev.to/alexalexyang/how-to-use-markdown-in-pages-in-gatsby-5dee</guid>
      <description>&lt;p&gt;Most tutorials show how to create full pages with Gatsby's &lt;code&gt;createPages&lt;/code&gt;, like for blogs. However, what if we don't want to create pages? What if we want to take markdown from markdown files and insert them into pages instead? That is, how do we insert markdown into JSX? I haven't found a lot of details on this. So, this is a fairly quick piece on how to do it.&lt;/p&gt;

&lt;p&gt;In this article, we will display markdown from a markdown file in the default index.js landing page.&lt;/p&gt;

&lt;p&gt;I kind of wrote this from memory. Please let me know if I made a mistake, missed anything, or if you have a faster method.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why put markdown in pages?
&lt;/h1&gt;

&lt;p&gt;I work with non-tech people. Sometimes, we want an SSG instead of a CMS. Markdown is easy to learn and write with.&lt;/p&gt;

&lt;p&gt;This lets content folks publish with ease. They can upload markdown files and edit them whenever they like.&lt;/p&gt;

&lt;h1&gt;
  
  
  Basic setup
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Create a new project
&lt;/h2&gt;

&lt;p&gt;Set up the default starter project with &lt;code&gt;gatsby new myproj&lt;/code&gt;. You can read more about starting a project &lt;a href="https://www.gatsbyjs.org/docs/quick-start/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install plugins
&lt;/h2&gt;

&lt;p&gt;We need to install gatsby-source-filesystem to help Gatsby access the markdown files that we'll soon create. So, do this in your project root directory: &lt;code&gt;npm install gatsby-source-filesystem&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Tell Gatsby about this new plugin by adding this to the gatsby-config.js file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;plugins: [
  {
    resolve: `gatsby-source-filesystem`,
    options: {
      name: `markdown-pages`,
      path: `${__dirname}/src/markdown-pages`,
    },
  },
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, install gatsby-transformer-remark, which lets Gatsby use markdown properly: &lt;code&gt;npm install gatsby-transformer-remark&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Add it to gatsby-config.js:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;plugins: [
  {
    resolve: `gatsby-source-filesystem`,
    options: {
      path: `${__dirname}/src/markdown-pages`,
      name: `markdown-pages`,
    },
  },
  `gatsby-transformer-remark`,
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;path&lt;/code&gt;. It's &lt;code&gt;${__dirname}/src/markdown-pages&lt;/code&gt;. This means Gatsby should look into /src/markdown-pages for markdown files. Create the markdown-pages directory in src.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make a markdown file
&lt;/h2&gt;

&lt;p&gt;Create a markdown file in /src/markdown-pages. For our current demo purpose, let's create one called home.md.&lt;/p&gt;

&lt;p&gt;Examples online usually tell us to create something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
path: "/"
date: "2019-10-06"
title: "About"
---

# About us

Here is the body of our content. It will be magically turned into HTML.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The part in &lt;code&gt;---&lt;/code&gt; is the frontmatter. We place metadata in there.&lt;/p&gt;

&lt;h1&gt;
  
  
  Graphql code
&lt;/h1&gt;

&lt;p&gt;We'll make this really simple. We'll place our code in index.js so everything loads on the landing page. Find it in /src/pages/index.js.&lt;/p&gt;

&lt;p&gt;We use graphql to look for the markdown file /src/markdown-pages/home.md. After checking out a handful of solutions, such as &lt;a href="https://github.com/gatsbyjs/gatsby/issues/9043"&gt;here&lt;/a&gt; and &lt;a href="https://github.com/gatsbyjs/gatsby/issues/1634"&gt;here&lt;/a&gt;, I decided that the &lt;code&gt;allMarkdownRemark&lt;/code&gt; method is probably easiest. Feel free to let me know if you have an even easier option.&lt;/p&gt;

&lt;p&gt;I haven't figured out how to fully properly use regex in graphql yet but we'll use it as shown below. It should work for files named in simple ways.&lt;/p&gt;

&lt;p&gt;Here's the query to get the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;graphql`
query {
  allMarkdownRemark(
    filter: {fileAbsolutePath: {regex: "/home/"}}
  ) {
      ...
    }
}
`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the code above, the regex bit looks for file names with "home" in it. You might want to tailor the regex to your needs. It's currently fine for me because I'm using it for a simple site with just Home, Contact, and About pages.&lt;/p&gt;

&lt;p&gt;Here's how to get the markdown content in home.md:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;graphql`
query {
  allMarkdownRemark(
    filter: {fileAbsolutePath: {regex: "/home/"}}
  ) {
      edges {
        node {
          frontmatter {
            title
            date(formatString: "DD MMMM YYYY")
          }
          html
        }
      }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the code above, we've extracted a couple of frontmatter metadata. We've left out &lt;code&gt;path&lt;/code&gt; because it's not needed today. We've also taken the html content.&lt;/p&gt;

&lt;p&gt;It's not done yet though. We have to export it. So, the whole code looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const query = graphql`
query {
  allMarkdownRemark(
    filter: {fileAbsolutePath: {regex: "/home/"}}
  ) {
      edges {
        node {
          frontmatter {
            title
            date(formatString: "DD MMMM YYYY")
          }
          html
        }
      }
    }
}
`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Place it just above &lt;code&gt;export default IndexPage&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Query response is in props
&lt;/h1&gt;

&lt;p&gt;The data from the graphql query will be in props. So, let's put it into &lt;code&gt;IndexPage&lt;/code&gt; with &lt;code&gt;{ data }&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const IndexPage = ({ data }) =&amp;gt; (
  ...
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's also pass into &lt;code&gt;IndexPage&lt;/code&gt; a method we'll use to read and display the markdown properly. We'll call it &lt;code&gt;md()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To make things simpler, let's get rid of almost all the default JSX in &lt;code&gt;IndexPage&lt;/code&gt; and just have &lt;code&gt;{md({ data })}&lt;/code&gt; in it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const IndexPage = ({ data }) =&amp;gt; (
  &amp;lt;Layout&amp;gt;
    {md({ data })}
  &amp;lt;/Layout &amp;gt;
)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feel free to add other JSX to it if you like.&lt;/p&gt;

&lt;h1&gt;
  
  
  Method to read markdown
&lt;/h1&gt;

&lt;p&gt;Before we continue, let's note that the structure of the data follows the graphql query we made above. After some exploration, I found out that &lt;code&gt;allMarkdownRemark&lt;/code&gt; returns an array. I then used both the graphql playground and console in developer tools to navigate the structure properly. Despite that, it still takes some experimentation to get right.&lt;/p&gt;

&lt;p&gt;In the interest of time, I'll just tell you the markdown content we want is in &lt;code&gt;data.data.allMarkdownRemark.edges[0].node&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, let's build the function &lt;code&gt;md()&lt;/code&gt;. It has two important parts:&lt;/p&gt;

&lt;p&gt;First, we destructure &lt;code&gt;frontmatter&lt;/code&gt; and &lt;code&gt;html&lt;/code&gt; from the props.&lt;/p&gt;

&lt;p&gt;Second, we return the JSX we want with the markdown properly rendered.&lt;/p&gt;

&lt;p&gt;Here's how it looks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const md = (data) =&amp;gt; {
  const { frontmatter, html } = data.data.allMarkdownRemark.edges[0].node

  return (&amp;lt;div&amp;gt;
    &amp;lt;h1&amp;gt;{frontmatter.title}&amp;lt;/h1&amp;gt;
    &amp;lt;p&amp;gt;{frontmatter.date}&amp;lt;/p&amp;gt;
    &amp;lt;div
      dangerouslySetInnerHTML={{ __html: html }}
    /&amp;gt;
  &amp;lt;/div&amp;gt;)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  The whole index.js
&lt;/h1&gt;

&lt;p&gt;By now, our code should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from "react"
import { graphql } from "gatsby"

import Layout from "../components/layout"

const md = (data) =&amp;gt; {
  const { frontmatter, html } = data.data.allMarkdownRemark.edges[0].node
  console.log(frontmatter.title)
  return (&amp;lt;div&amp;gt;
    &amp;lt;h1&amp;gt;{frontmatter.title}&amp;lt;/h1&amp;gt;
    &amp;lt;p&amp;gt;{frontmatter.date}&amp;lt;/p&amp;gt;
    &amp;lt;div
      dangerouslySetInnerHTML={{ __html: html }}
    /&amp;gt;
  &amp;lt;/div&amp;gt;)
}

const IndexPage = ({ data }) =&amp;gt; (
  &amp;lt;Layout&amp;gt;
    {md({ data })}
  &amp;lt;/Layout &amp;gt;
)

export const query = graphql`
query {
  allMarkdownRemark(
    filter: {fileAbsolutePath: {regex: "/home/"}}
  ) {
      edges {
        node {
          frontmatter {
            title
            date(formatString: "DD MMMM YYYY")
          }
          html
        }
      }
    }
}
`

export default IndexPage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;gatsby develop&lt;/code&gt; and check out the page. It should be at localhost:8000.&lt;/p&gt;

</description>
      <category>gatsby</category>
      <category>markdown</category>
      <category>graphql</category>
    </item>
    <item>
      <title>Starting with saleor-storefront</title>
      <dc:creator>alex</dc:creator>
      <pubDate>Fri, 20 Sep 2019 15:54:13 +0000</pubDate>
      <link>https://dev.to/alexalexyang/starting-with-saleor-storefront-44e7</link>
      <guid>https://dev.to/alexalexyang/starting-with-saleor-storefront-44e7</guid>
      <description>&lt;p&gt;This is to document how I got started with &lt;a href="https://github.com/mirumee/saleor-storefront"&gt;saleor-storefront&lt;/a&gt; 0.6.0. This is &lt;a href="https://github.com/mirumee/saleor"&gt;saleor&lt;/a&gt; 0.0.0's front end made with React.&lt;/p&gt;

&lt;p&gt;In this small walkthrough, I talk about a bunch of gotchas I encountered while trying to deploy them both. I hope this makes getting started with saleor a wee bit easier.&lt;/p&gt;

&lt;p&gt;Most of the material online suggests that the writers are running both storefront and saleor on the same machine. I could not get this to work due to a few issues, including that the storefront won't run as described &lt;a href="https://github.com/mirumee/saleor-storefront/issues/470"&gt;here&lt;/a&gt; by AbdulWaheedPasha.&lt;/p&gt;

&lt;p&gt;Before AbdulWaheedPasha let me know about this issue, I thought it was because my many projects were putting too much strain on my Ubuntu instance. So, I decided to lighten the load a little bit and deploy the storefront to Netlify instead.&lt;/p&gt;

&lt;p&gt;I start from the point just after we've cloned the saleor repo into our Ubuntu machine, and we've either forked or pushed the saleor-storefront to our own Git repo.&lt;/p&gt;

&lt;p&gt;However, before deploying to Netlify, we have to set ALLOWED_HOSTS properly. This is done on the saleor site of things.&lt;/p&gt;

&lt;h1&gt;
  
  
  Set ALLOWED_HOSTS to *
&lt;/h1&gt;

&lt;p&gt;I want to deploy saleor to &lt;a href="https://example.com"&gt;https://example.com&lt;/a&gt; on my Ubuntu instance and saleor-storefront to &lt;a href="https://storefront.example.com"&gt;https://storefront.example.com&lt;/a&gt; on Netlify.&lt;/p&gt;

&lt;p&gt;In this case, because I run saleor by executing &lt;code&gt;docker-compose up&lt;/code&gt; (sometimes with &lt;code&gt;-d&lt;/code&gt;), I decided to add &lt;code&gt;ENV ALLOWED_HOSTS 'storefront.example.com'&lt;/code&gt; to saleor/Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EXPOSE 8000
ENV PORT 8000
ENV PYTHONUNBUFFERED 1
ENV PROCESSES 4
ENV ALLOWED_HOSTS 'storefront.example.com'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This did not work. Storefront kept returning "Invalid host header" or otherwise just a blank page sometimes. So, I tried &lt;code&gt;ENV ALLOWED_HOSTS 'https://storefront.example.com'&lt;/code&gt; instead. Did not work.&lt;/p&gt;

&lt;p&gt;Most solutions online say to set it to &lt;code&gt;*&lt;/code&gt;, which I felt might be a bit of a security issue because it would allow anyone to take data from the saleor back end. I went with it anyway, seeing how I couldn't solve the problem with a specific URL. It now looks like this: &lt;code&gt;ENV ALLOWED_HOSTS '*'&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Before we go on, remember to run commands like &lt;code&gt;npm run build-assets&lt;/code&gt; and &lt;code&gt;npm run build-emails&lt;/code&gt; in accordance with the &lt;a href="https://docs.saleor.io/docs/getting-started/intro/"&gt;docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You should also run &lt;code&gt;python3 manage.py migrate&lt;/code&gt; and &lt;code&gt;python3 manage.py createsuperuser&lt;/code&gt; to make an admin for your site. If you also chose to use &lt;code&gt;docker-compose up&lt;/code&gt; like I did, you can enter the saleor container with &lt;code&gt;docker exec -it saleor_web_1 bash&lt;/code&gt;. Then run the migrate and createsuperuser commands.&lt;/p&gt;

&lt;p&gt;Next, we turn to the saleor-storefront side of things.&lt;/p&gt;

&lt;h1&gt;
  
  
  Deploying saleor-storefront to Netlify
&lt;/h1&gt;

&lt;p&gt;This should be pretty straightforward, I think. Follow Netlify's instructions to deploy repos. I will focus on storefront-specific things here.&lt;/p&gt;

&lt;p&gt;Your deployment settings are in site settings &amp;gt; build and deploy. They should look like this:&lt;/p&gt;

&lt;p&gt;Build command: &lt;code&gt;npm run-script build&lt;/code&gt;&lt;br&gt;
Publish directory: dist&lt;/p&gt;

&lt;p&gt;It should build successfully, but the site will encounter a couple of problems, discussed below.&lt;/p&gt;
&lt;h1&gt;
  
  
  Make sure BACKEND_URL is properly set
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Update 7 Oct 2019: This has been changed to &lt;code&gt;API_URI&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The storefront &lt;a href="https://github.com/mirumee/saleor-storefront#prerequisites"&gt;prerequisites&lt;/a&gt; says: "To run the storefront, you have to set the BACKEND_URL environment to point to the Saleor instance. If you are running Saleor locally with the default settings, set BACKEND_URL to: &lt;a href="http://localhost:8000/"&gt;http://localhost:8000/&lt;/a&gt;."&lt;/p&gt;

&lt;p&gt;This was not straightforward. Let's recap before continuing: I've deployed saleor to &lt;a href="https://example.com"&gt;https://example.com&lt;/a&gt; on my Ubuntu instance and storefront to &lt;a href="https://storefront.example.com"&gt;https://storefront.example.com&lt;/a&gt; on Netlify.&lt;/p&gt;

&lt;p&gt;To set the &lt;code&gt;BACKEND_URL&lt;/code&gt;, I ran &lt;code&gt;export BACKEND_URL=https://example.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Howevever, it returned 404. Looking into console, I found the requested URL to be: &lt;a href="https://storefront.example.comhttps://example.com/graphql/"&gt;https://storefront.example.comhttps://example.com/graphql/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Somehow, it was concatenating the storefront URL to the saleor back end URL.&lt;/p&gt;

&lt;p&gt;I thought maybe I had to delete the storefront URL from the graphql query code. Since it's graphql, I thought I had to set the URL in saleor-storefront/apollo.config.js. This was wrong.&lt;/p&gt;

&lt;p&gt;After many tries I eventually figured out that I had to open up saleor-storefront/config/webpack/config.base.js and find this section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;new webpack.EnvironmentPlugin({
      "BACKEND_URL": "http://localhost:8000/",
      "SERVICE_WORKER_TIMEOUT": "60000"
    })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I then thought I could simple unset the &lt;code&gt;BACKEND_URL&lt;/code&gt; environmental variable and place the correct URL in here. This only reproduced the problem.&lt;/p&gt;

&lt;p&gt;The solution is to set the &lt;code&gt;BACKEND_URL&lt;/code&gt; environmental variable and completely delete the URL in config.base.js so it's empty:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"BACKEND_URL": "",
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, it worked.&lt;/p&gt;

&lt;h1&gt;
  
  
  Products page won't load, then 404
&lt;/h1&gt;

&lt;p&gt;Once the site was sort of properly up, I tried accessing a product I'd made in the admin dashboard earlier. The page would not load due to a TypeError. Something about &lt;code&gt;price&lt;/code&gt; being &lt;code&gt;undefined&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After reloading the page a couple of times, it returned 404 and remained that way.&lt;/p&gt;

&lt;p&gt;I tried a whole slew of things I don't remember anymore and finally decided to start from scratch. I ran &lt;code&gt;docker-compose down&lt;/code&gt; on the saleor back end, deleted the saleor_web_1 container and saleor_web image that are for the saleor Django back end. I left the redis and celery containers and images alone. I then ran &lt;code&gt;docker-compose up&lt;/code&gt; again.&lt;/p&gt;

&lt;p&gt;Now it works. But... it loads without the product description.&lt;/p&gt;

&lt;p&gt;And, this is where I am right now.&lt;/p&gt;

&lt;p&gt;I hope to write as I go along solving issues. Please let me know if you can help with on-going problems.&lt;/p&gt;

</description>
      <category>saleor</category>
    </item>
  </channel>
</rss>
