<?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: Julien Bras</title>
    <description>The latest articles on DEV Community by Julien Bras (@julbrs).</description>
    <link>https://dev.to/julbrs</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%2F252757%2F9d37a1e5-8385-4446-9ab0-7b0b2ac6a012.JPG</url>
      <title>DEV Community: Julien Bras</title>
      <link>https://dev.to/julbrs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/julbrs"/>
    <language>en</language>
    <item>
      <title>How to migrate from Jest to Vitest without headaches!</title>
      <dc:creator>Julien Bras</dc:creator>
      <pubDate>Sun, 17 Dec 2023 20:45:14 +0000</pubDate>
      <link>https://dev.to/julbrs/how-to-migrate-from-jest-to-vitest-without-headaches-4f20</link>
      <guid>https://dev.to/julbrs/how-to-migrate-from-jest-to-vitest-without-headaches-4f20</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://vitest.dev"&gt;Vitest&lt;/a&gt; is a testing library that is API-compatible with Jest. The main advantages over &lt;a href="https://jestjs.io"&gt;Jest&lt;/a&gt; are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vitest is compatible out-of-the-box with &lt;a href="https://www.typescriptlang.org"&gt;TypeScript&lt;/a&gt;! No need to deal with &lt;code&gt;ts-jest&lt;/code&gt; or other complex setup.&lt;/li&gt;
&lt;li&gt;Vitest is fast! We have seen a improvement of around 50% on your main module!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After a few months with both Vitest and Jest in our team, we have decided to give a push and migrate the remaining projects to Vitest only. This will help us to be more efficient on all the projects (it's not always easy to switch between Jest testing and Vitest testing depending on the project!).&lt;/p&gt;

&lt;h2&gt;
  
  
  The issue
&lt;/h2&gt;

&lt;p&gt;For a majority of the projects, we have a small number of test files (between 1 and 10). So it's a quick win to migrate from one testing framework to another. In each test file a couple of modification is needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;import the &lt;code&gt;describe&lt;/code&gt;, &lt;code&gt;it&lt;/code&gt;,&lt;code&gt;expect&lt;/code&gt; keywords from &lt;code&gt;vitest&lt;/code&gt; as it's not global by default&lt;/li&gt;
&lt;li&gt;adjust the test if not passing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But for one project, this method seems too complex. The project is a NextJs application:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;82 test files&lt;/li&gt;
&lt;li&gt;257 tests in total&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's pretty obvious that a single Pull Request will be not a good idea, and it may take too much time to work on this task. In the team, we are not using the &lt;a href="https://trunkbaseddevelopment.com"&gt;Trunk-Based development&lt;/a&gt; methodology, but we try to keep our modifications small:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;not too much code modified each time&lt;/li&gt;
&lt;li&gt;not too much time between starting creating a feature branch and the merge of the feature (about a day)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For us, it's a good way to keep the development flow and not struggle too much with merging issues. So switching 250 tests in a day doesn't seems to be a realistic objective, even for an experimented developer! Can we find a alternative path?&lt;/p&gt;

&lt;h2&gt;
  
  
  The baby step idea
&lt;/h2&gt;

&lt;p&gt;As usual when an issue seems too complex, a good approach is to split the problem. Here the best solution we found is to split our migration in multiple &lt;em&gt;smaller&lt;/em&gt; steps. But splitting test environment doesn't seems to be easy, right? Okay let's describe the methodology!&lt;/p&gt;

&lt;h3&gt;
  
  
  First step: init the Vitest setup
&lt;/h3&gt;

&lt;p&gt;The first step is to install Vitest without removing Jest. So in the project let's start by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add vitest --dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new &lt;code&gt;vite.config.ts&lt;/code&gt; file with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fileURLToPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vitest/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;setupFiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./vitest-setup.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jsdom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// TODO: remove include prop after complete Vitest migration&lt;/span&gt;
    &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tests-vitest/**/*.{test,spec}.?(c|m)[jt]s?(x)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;coverage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;reporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lcov&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;outputFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;coverage/sonar-report.xml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;find&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;replacement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;fileURLToPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a new &lt;code&gt;vitest-setup.ts&lt;/code&gt; with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@testing-library/jest-dom/vitest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(to be able to use &lt;a href="https://github.com/testing-library/jest-dom"&gt;jest-dom&lt;/a&gt; !)&lt;/p&gt;

&lt;p&gt;The idea is too keep all old Jest test in the &lt;code&gt;test/&lt;/code&gt; folder, and move slowly the tests in a new &lt;code&gt;tests-vitest/&lt;/code&gt; folder. We need to set the &lt;code&gt;include&lt;/code&gt; prop in the configuration to run only the tests in the specific folder. We are also configuring some coverage with Sonar.&lt;/p&gt;

&lt;p&gt;Here is the architecture of the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;web/
    components/.       # React components
    pages/             # Still using the pages router of NextJs!
    tests/             # Jest tests
    test-vitest/       # Vitest tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;in &lt;code&gt;package.json&lt;/code&gt;, we are adding a new command to run Vitest tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test:vitest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vitest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So we can run Jest tests with &lt;code&gt;yarn test&lt;/code&gt; and Vitest tests with &lt;code&gt;yarn test:vitest&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;A final modification, we need to tweak the Jest configuration in &lt;code&gt;jest.config.js&lt;/code&gt; to search tests only in the &lt;code&gt;tests/&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="na"&gt;testMatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;rootDir&amp;gt;/tests/**/?(*.)+(spec|test).[jt]s?(x)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we can drop a few first tests in &lt;code&gt;tests-vitest&lt;/code&gt; to be sure it's working! The first PR goal was about 4 tests files migrated, so it was a pretty small PR that we were able to validate in a day.&lt;/p&gt;

&lt;p&gt;Finally don't forget to adjust your CI to run &lt;strong&gt;both&lt;/strong&gt; test suite in order to still have a complete coverage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Second step: increase the Vitest coverage
&lt;/h3&gt;

&lt;p&gt;Then slowly move a couple of test files from &lt;code&gt;tests/&lt;/code&gt; to &lt;code&gt;tests-vitest/&lt;/code&gt; and adjust tests to make then pass. Again the same strategy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add the imports for &lt;code&gt;describe&lt;/code&gt;, &lt;code&gt;it&lt;/code&gt;, &lt;code&gt;expect&lt;/code&gt;, or &lt;code&gt;vi&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;play a bit with the test&lt;/li&gt;
&lt;li&gt;and &lt;em&gt;voilà&lt;/em&gt;!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It took us around &lt;strong&gt;11 Pull Requests&lt;/strong&gt; to move all &lt;strong&gt;82 test files and 257 tests&lt;/strong&gt;. Only one test have been declared too complicated and we decided (for now) to disable it, we will try to re-enable it later on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Third step: remove Jest!
&lt;/h3&gt;

&lt;p&gt;Finally it's time to remove all Jest dependencies! This is the most fun part of the project because you remove a lot dependencies! 🤓 Do not forget to remove also all configuration files (&lt;code&gt;jest.config.js&lt;/code&gt;, &lt;code&gt;jest.setup.js&lt;/code&gt;...) and we decided to move back all Vitest tests in &lt;code&gt;tests/&lt;/code&gt; in this specific PR (adjust &lt;code&gt;include&lt;/code&gt; in &lt;code&gt;vitest.config.ts&lt;/code&gt;!).&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The task seems at first sight impossible when you want to apply the same rules. But you are free to decide and create new way to work a specific problem! Here instead of dealing with a monster PR with maybe 100 or more modified files, it was much more easy to split the issue and have a temporary setup during the migration time. It took us around 2 weeks to complete the migration (around one PR per day during the migration process!)&lt;/p&gt;

</description>
      <category>vitest</category>
      <category>jest</category>
      <category>testing</category>
      <category>javascript</category>
    </item>
    <item>
      <title>🔒 Next Auth vs SST Auth</title>
      <dc:creator>Julien Bras</dc:creator>
      <pubDate>Mon, 21 Nov 2022 14:39:14 +0000</pubDate>
      <link>https://dev.to/julbrs/next-auth-vs-sst-auth-2lne</link>
      <guid>https://dev.to/julbrs/next-auth-vs-sst-auth-2lne</guid>
      <description>&lt;p&gt;In a previous article, I describe how to use &lt;a href="https://sidoine.org/oauth-with-serverless-using-sst/" rel="noopener noreferrer"&gt;SST Auth&lt;/a&gt; construct in order to implement an OAuth authentication workflow for your application. &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt;, the &lt;em&gt;React framework for production&lt;/em&gt; is also providing a component named &lt;a href="https://next-auth.js.org/" rel="noopener noreferrer"&gt;NextAuth.js&lt;/a&gt; that can be used to implement such authentication system. Let's compare the two solutions! 🤺&lt;/p&gt;

&lt;h2&gt;
  
  
  What is SST Auth?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://sst.dev/" rel="noopener noreferrer"&gt;SST&lt;/a&gt; is framework designed to build &lt;strong&gt;backend serverless applications&lt;/strong&gt; initially. I have already written a couple of articles on this solution (&lt;a href="https://sidoine.org/sst-the-most-underrated-serverless-framework-you-need-to-discover/" rel="noopener noreferrer"&gt;here&lt;/a&gt; and &lt;a href="https://sidoine.org/sst-the-most-underrated-serverless-framework-you-need-to-discover/" rel="noopener noreferrer"&gt;here&lt;/a&gt; for example). It provides features to deploy web applications too (for example via the &lt;a href="https://docs.sst.dev/constructs/StaticSite" rel="noopener noreferrer"&gt;StaticSite&lt;/a&gt; construct) so it's advertised as a tool to build &lt;em&gt;full-stack serverless applications&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.sst.dev/auth" rel="noopener noreferrer"&gt;Auth&lt;/a&gt; module is a dedicated set of components built by the SST team to implement an authentication system inside your application. It works well with a web application like a React app.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is NextAuth.js?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; is probably the most famous React-based framework available today, and gain a lot of visibility in the last few years. Today with version 13, it's truly a &lt;strong&gt;full-stack framework solution&lt;/strong&gt; with the support of server-side rendering options and an API layer. You can check for example the &lt;a href="https://www.youtube.com/watch?v=W4UhNo3HAMw" rel="noopener noreferrer"&gt;Theo Browne video: Next.js is a backend framework&lt;/a&gt; which is a good introduction to the backend part.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://next-auth.js.org/" rel="noopener noreferrer"&gt;NextAuth.js&lt;/a&gt; is an independent library (not supported by &lt;strong&gt;Vercel&lt;/strong&gt;), with the following motto: &lt;em&gt;"Authentication for Next.js"&lt;/em&gt; It provides a built-in solution to implement an authentication system for Next.js, based on OAuth protocol.&lt;/p&gt;

&lt;p&gt;You know the actors, it's now time to get to the comparison bullet points, let's fight 😇&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsbd991dtk0devlqj2yka.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsbd991dtk0devlqj2yka.jpeg" alt="Fight!" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Round 1: Supported Adapters/Providers!
&lt;/h2&gt;

&lt;p&gt;Nowadays, authentification is not only email-password credentials. It's more &lt;strong&gt;social logins&lt;/strong&gt; like Google, Facebook, or GitHub. It's more secure for the application developer (no more password to store!) and for the application end-user (no new password to remember!). Let's check what is supported by our two choices. &lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;SST Auth&lt;/strong&gt; is supporting out of the box today (November 2022) &lt;a href="https://docs.sst.dev/auth#adapters" rel="noopener noreferrer"&gt;seven adapters&lt;/a&gt;: Google, GitHub, Twitch, Facebook, Magic Link, OAuth, and OIDC. The last two are generic adapters that can be used for any application which is supporting &lt;a href="https://oauth.net/2/" rel="noopener noreferrer"&gt;OAuth2&lt;/a&gt; or &lt;a href="https://openid.net/connect/" rel="noopener noreferrer"&gt;OIDC&lt;/a&gt;. Finally, there is an option to build a &lt;a href="https://docs.sst.dev/auth#custom-adapters" rel="noopener noreferrer"&gt;Custom Adapter&lt;/a&gt; if nothing fit your needs. For example in this &lt;a href="https://sidoine.org/oauth-with-serverless-using-sst/" rel="noopener noreferrer"&gt;last article&lt;/a&gt;, I have built a custom adapter to support &lt;strong&gt;SmugMug&lt;/strong&gt;, which is relying on the &lt;strong&gt;OAuth 1.0a&lt;/strong&gt; protocol.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NextAuth.js&lt;/strong&gt; is supporting out the box more than &lt;a href="https://next-auth.js.org/providers/" rel="noopener noreferrer"&gt;20 providers&lt;/a&gt;: the classic ones like Google, Facebook, and GitHub are here, but there are more options compared to SST Auth. Additionally, there is also an &lt;a href="https://next-auth.js.org/configuration/providers/email" rel="noopener noreferrer"&gt;email&lt;/a&gt; provider (can be compared to the &lt;em&gt;Magic Link&lt;/em&gt; one on SST Auth) or a &lt;a href="https://next-auth.js.org/configuration/providers/oauth#using-a-custom-provider" rel="noopener noreferrer"&gt;custom provider&lt;/a&gt;. Finally, the &lt;a href="https://next-auth.js.org/configuration/providers/credentials" rel="noopener noreferrer"&gt;Credentials&lt;/a&gt; provider is an ideal solution if you need to login via username password, or other arbitrary credentials (YubiKey for example).&lt;/p&gt;

&lt;p&gt;➡️ There is much out-of-the-box options using &lt;strong&gt;NextAuth.js&lt;/strong&gt; compared to &lt;strong&gt;SST Auth&lt;/strong&gt;. But both solutions provide you customization if needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Round 2: Easiness To Implement!
&lt;/h2&gt;

&lt;p&gt;Authentication is a &lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html" rel="noopener noreferrer"&gt;serious&lt;/a&gt; subject, and you don't want to rely on a system that you don't trust 100%. That's why it's important to understand how to implement the solution, and how easily you will be able to understand it and tweak it ultimately.&lt;/p&gt;

&lt;p&gt;Let's get a look at &lt;strong&gt;SST Auth&lt;/strong&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you first need to add the &lt;code&gt;Auth&lt;/code&gt; construct in your infrastructure stack (generally the &lt;code&gt;stacks/MyStack.ts&lt;/code&gt; file)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Auth&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@serverless-stack/resources&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Auth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;authenticator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;functions/auth.handler&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;myApi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// optional&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;then you can build a new serverless function in &lt;code&gt;src/functions/auth.ts&lt;/code&gt; that will be responsible for all the authentication calls (&lt;code&gt;authorize&lt;/code&gt; and &lt;code&gt;callback&lt;/code&gt; endpoints for OAuth2 for example):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AuthHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GoogleAdapter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@serverless-stack/node/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AuthHandler&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;google&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;GoogleAdapter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;oidc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;clientID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;XXXX&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;onSuccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tokenset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tokenset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example case (from the &lt;a href="https://docs.sst.dev/auth#add-a-handler" rel="noopener noreferrer"&gt;Auth documentation&lt;/a&gt;) is creating an authentication system based on Google SignIn. It will expose 2 endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/auth/google/authorize&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;/auth/google/callback&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finally, it's up to your frontend application (React for example) to redirect the user to the &lt;code&gt;autorize&lt;/code&gt; backend endpoint (this will start the OAuth2 flow).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's now zoom on &lt;strong&gt;NextAuth.js&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, you need to install NextAuth.js in your Next.js project:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add next-auth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Then you have to create a new API in your Next project, inside the designed &lt;code&gt;pages/api/&lt;/code&gt; folder. So create a file named &lt;code&gt;pages/api/auth/[...nextauth].js&lt;/code&gt; with the following content:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;NextAuth&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next-auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;GoogleProvider&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next-auth/providers/google&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Configure one or more authentication providers&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;GoogleProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nc"&gt;NextAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This API proxy endpoint is the exact equivalent of the &lt;code&gt;auth.ts&lt;/code&gt; created for SST. &lt;strong&gt;NextAuth.js&lt;/strong&gt; will take care to create multiple endpoints for &lt;code&gt;authorize&lt;/code&gt; and &lt;code&gt;callback&lt;/code&gt; in order to support OAuth2.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Then it's advised to implement the &lt;code&gt;SessionProvider&lt;/code&gt; at the top level of the application (in &lt;code&gt;pages/_app.jsx&lt;/code&gt;):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SessionProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next-auth/react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;pageProps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;pageProps&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SessionProvider&lt;/span&gt; &lt;span class="na"&gt;session&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Component&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;pageProps&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;SessionProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;And finally use the dedicated methods provided by &lt;strong&gt;NextAuth.js&lt;/strong&gt; to allow the end-user to sign in or sign out:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useSession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signIn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signOut&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next-auth/react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSession&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
        Signed in as &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;signOut&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Sign out&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      Not signed in &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;signIn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Sign in&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This article will not go deeper into the implementation, but each solution is providing a session system to retrieve the user data on the frontend and on the backend side. It's also possible to prevent access for not authenticated users.&lt;/p&gt;

&lt;p&gt;➡️ To conclude on this aspect, both solutions are simple enough to be used by a frontend developer, and it does not require a big amount of code to be used. The documentation provided is also very complete. It's a draw!&lt;/p&gt;

&lt;h2&gt;
  
  
  Round 3: Store User-Related Information!
&lt;/h2&gt;

&lt;p&gt;Once authentication is implemented, you may need to store some information relative to each user. For exemple, the profile section needs to be persisted, or any pertinent information for your precise business case. Again both solutions come with a solution for that.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;SST Auth&lt;/strong&gt;, it's fairly natural to rely on a distinct SST construct, &lt;a href="https://docs.sst.dev/constructs/Table" rel="noopener noreferrer"&gt;Table&lt;/a&gt; that is relying on AWS DynamoDB. By implementing the &lt;code&gt;onSuccess&lt;/code&gt; method available in each adapter, it's possible to store the user data in DynamoDB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AuthHandler&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;smugmug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;SmugMugAdapter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SMUGMUG_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SMUGMUG_CLIENT_SECRET&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;onSuccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SmugMugUser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ddb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DynamoDBClient&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ddb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PutItemCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;TableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;marshall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parameter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IS_LOCAL&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://127.0.0.1:3000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ViteStaticSite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Example took from &lt;a href="https://sidoine.org/oauth-with-serverless-using-sst/" rel="noopener noreferrer"&gt;OAuth with Serverless using SST&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;NextAuthjs&lt;/strong&gt;, it's possible to implement an &lt;a href="https://next-auth.js.org/adapters/overview" rel="noopener noreferrer"&gt;adapter&lt;/a&gt; (⚠️ NextAuthjs adapter is not the same as an SST Auth adapter!). There are more than 10 options, including DynamoDB, Firebase, Prisma, FaunaDB... Let's zoom in on DynamoDB adapter here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install the corresponding adapter:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add next-auth @next-auth/dynamodb-adapter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Edit your existing API in &lt;code&gt;pages/api/auth/[...nextauth].js&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DynamoDB&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/client-dynamodb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBDocument&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/lib-dynamodb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;NextAuth&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next-auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Providers&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next-auth/providers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBAdapter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@next-auth/dynamodb-adapter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBClientConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;accessKeyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_AUTH_AWS_ACCESS_KEY&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;secretAccessKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_AUTH_AWS_SECRET_KEY&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_AUTH_AWS_REGION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DynamoDB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;marshallOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;convertEmptyValues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;removeUndefinedValues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;convertClassInstanceToMap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nc"&gt;NextAuth&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;Providers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Google&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DynamoDBAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will have more information on the adapter &lt;a href="https://next-auth.js.org/adapters/dynamodb" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Take note that you will need to create the DynamoDB table before using it (NextAuth.js does not handle the creation of the resource).&lt;/p&gt;

&lt;p&gt;➡️ Both solutions implement options to store information relative to the user. SST relies on &lt;em&gt;constructs&lt;/em&gt; and NextAuth relies on &lt;em&gt;adapters&lt;/em&gt;. SST is a more &lt;em&gt;integrated&lt;/em&gt; solution because it will handle the creation of the resource (infrastructure as code built-in). No clear winner here again!&lt;/p&gt;

&lt;h2&gt;
  
  
  Round 4: Deploy Options!
&lt;/h2&gt;

&lt;p&gt;Now it's time to ship your application 🚀 ! How easy is the process for each solution? Let's find it!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SST Auth&lt;/strong&gt; is part of &lt;strong&gt;SST&lt;/strong&gt; obviously. This serverless framework is designed to be used with &lt;strong&gt;AWS&lt;/strong&gt;. So you need to rely on this specific cloud provider, but it comes with a dedicated CLI command to push your application. As &lt;a href="https://docs.sst.dev/quick-start#4-deploy-to-prod" rel="noopener noreferrer"&gt;documented&lt;/a&gt;, this command will send your application to the &lt;del&gt;heavens&lt;/del&gt; cloud:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn deploy --stage prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;It's recommended to use &lt;strong&gt;a specific stage&lt;/strong&gt; for the production environment as SST is designed to use multiple instances: when you develop on the application you are using a distinct stage (like dev) with the powerful &lt;a href="https://docs.sst.dev/live-lambda-development" rel="noopener noreferrer"&gt;Live Lambda Development&lt;/a&gt; mode. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Regarding &lt;strong&gt;NextAuth.js&lt;/strong&gt;, you will have to deploy a &lt;strong&gt;Next.js&lt;/strong&gt; application. The short path here is to deploy your application on &lt;a href="https://vercel.com/" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt;, the creators of &lt;strong&gt;Next.js&lt;/strong&gt;. It comes with a pretty generous free tier, and it works out-of-the-box: automatic deployments with GitHub, preview deployments, etc... But a Next.js application can be also deployed in many other providers. For your own managed Node server to serverless deployment, it's up to you to choose the right one! See the complete list on &lt;a href="https://nextjs.org/docs/deployment" rel="noopener noreferrer"&gt;Next.js documentation page&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Let's mention that it's possible to deploy a &lt;strong&gt;Next.js&lt;/strong&gt; application using &lt;strong&gt;SST&lt;/strong&gt; 😅.&lt;br&gt;
This setup relies on the &lt;a href="https://github.com/serverless-nextjs/serverless-next.js" rel="noopener noreferrer"&gt;serverless-next.js&lt;/a&gt; project and it's hidden behind a construct named &lt;a href="https://docs.sst.dev/constructs/NextjsSite" rel="noopener noreferrer"&gt;NextJsSite&lt;/a&gt;. It can be a very efficient solution when you want to ship your application on AWS environment (I use it for a production workload in my current company).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;➡️ To conclude on this section, &lt;strong&gt;SST&lt;/strong&gt; is by design more restricted on the deployment (&lt;strong&gt;AWS&lt;/strong&gt; only). For &lt;strong&gt;Next.js&lt;/strong&gt;, the classic way is to rely on &lt;strong&gt;Vercel&lt;/strong&gt; but other options are available (including SST itself on AWS).&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;It's by design not fair to compare tools that are not in the same category! But I think it's useful sometimes to compare a specific feature (here authentication) as the implementation in both solutions is very comparable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SST&lt;/strong&gt; is a really good option when you need to build an application that will have to rely on multiple AWS services; it's possible to define using &lt;em&gt;Infrastructure as Code&lt;/em&gt; and then consume a &lt;a href="https://docs.sst.dev/constructs/Queue" rel="noopener noreferrer"&gt;Queue&lt;/a&gt;, a &lt;a href="https://docs.sst.dev/constructs/Bucket" rel="noopener noreferrer"&gt;Bucket&lt;/a&gt;, a &lt;a href="https://docs.sst.dev/constructs/RDS" rel="noopener noreferrer"&gt;Database&lt;/a&gt; to name a few available constructs. It's the most extensible solution here. Read my introduction to SST here: &lt;a href="https://sidoine.org/sst-the-most-underrated-serverless-framework-you-need-to-discover/" rel="noopener noreferrer"&gt;SST is The Most Underrated Serverless Framework You Need to Discover&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NextAuth.js&lt;/strong&gt; is &lt;em&gt;just&lt;/em&gt; a library, and it's more quick to add the authentication layer in an existing &lt;strong&gt;Next.js&lt;/strong&gt; application with this solution. It's the most integrated solution here.&lt;/p&gt;

&lt;p&gt;I recommend playing with the two solutions here and giving me your feedback on &lt;a href="https://twitter.com/_julbrs" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; (if it's still up and running when you read it 😅).&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>opensource</category>
      <category>llm</category>
    </item>
    <item>
      <title>OAuth with Serverless using SST</title>
      <dc:creator>Julien Bras</dc:creator>
      <pubDate>Sun, 13 Nov 2022 19:31:29 +0000</pubDate>
      <link>https://dev.to/julbrs/oauth-with-serverless-using-sst-13n3</link>
      <guid>https://dev.to/julbrs/oauth-with-serverless-using-sst-13n3</guid>
      <description>&lt;h2&gt;
  
  
  Why we Need Authentication in a Serverless Application?
&lt;/h2&gt;

&lt;p&gt;Once you have built your first serverless application, you may need to quickly invest effort in a authentication layer. For example on &lt;a href="https://dev.to/julbrs/why-and-how-migrate-from-firebase-to-serverless-stack-40e1"&gt;Why and How Migrate From Firebase to Serverless Stack?&lt;/a&gt; we choose to use an authentication based on &lt;strong&gt;Google&lt;/strong&gt; in order to save some data relative to each user of our application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it's a Challenge?
&lt;/h2&gt;

&lt;p&gt;It's a very classic use-case to implement an authentication system based on third-part actors, like Google or Facebook. Instead of managing locally a users and passwords  system, it's generally more secure to allow a login via Google, and then Google servers is validating to our systems that the login is a success. There is multiple advantage here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the end-user have just to use his own Google login to connect to your application, and there is no need to create and store a new password&lt;/li&gt;
&lt;li&gt;your application doesn't have to manage and secure a password system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This classic scenario is using generally a standard named &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/OAuth"&gt;OAuth&lt;/a&gt;&lt;/strong&gt;, built to allow access delegation without sharing private credentials. As an example here is an abstract flow of the OAuth2 protocol found on Wikipedia:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--y24Pb-GS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://upload.wikimedia.org/wikipedia/commons/7/72/Abstract-flow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--y24Pb-GS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://upload.wikimedia.org/wikipedia/commons/7/72/Abstract-flow.png" alt="OAuth Abstract Flow" width="800" height="578"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is a series of steps to achieve (about 5 or 6) and it's pretty tricky to implement this flow in a serverless stack, because such functions are by definition &lt;a href="https://sst.dev/chapters/what-is-aws-lambda.html"&gt;stateless&lt;/a&gt;: how to store temporary the authorization grant for example? That's why we generally rely on an authentication framework!&lt;/p&gt;

&lt;h2&gt;
  
  
  What are the Options?
&lt;/h2&gt;

&lt;p&gt;If you want to stick to serverless deployment, there is multiple options to implement an Authentication layer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://sst.dev/"&gt;SST&lt;/a&gt; is providing multiple constructs (&lt;a href="https://docs.sst.dev/constructs/Cognito"&gt;&lt;code&gt;Cognito&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://docs.sst.dev/constructs/Auth"&gt;&lt;code&gt;Auth&lt;/code&gt;&lt;/a&gt;) for this precise request&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt;, which can be &lt;a href="https://www.youtube.com/watch?v=W4UhNo3HAMw"&gt;considered as a full-stack platform&lt;/a&gt; thanks to the backend capabilities, comes with &lt;a href="https://next-auth.js.org/"&gt;NextAuth.js&lt;/a&gt; which is a promising solution to implement authentication in the platform.&lt;/li&gt;
&lt;li&gt;It's also possible to build a custom layer for the authentication, but we will not expend here on this option as it's the most time-consuming one 😅&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;[!NOTE] NextAuth vs SST Auth&lt;br&gt;
I plan to post an article to compare the 2 options. &lt;a href="https://twitter.com/_julbrs"&gt;Stay tuned!&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Let's Focus on SST Auth
&lt;/h2&gt;

&lt;p&gt;This article will be focused on &lt;strong&gt;SST &lt;a href="https://docs.sst.dev/constructs/Auth"&gt;&lt;code&gt;Auth&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; construct only: The &lt;a href="https://docs.sst.dev/constructs/Cognito"&gt;&lt;code&gt;Cognito&lt;/code&gt;&lt;/a&gt; construct (was named &lt;code&gt;Auth&lt;/code&gt; &lt;a href="https://docs.sst.dev/constructs/v0/Auth"&gt;before SST 1.0&lt;/a&gt; ) is based on &lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools.html"&gt;AWS Cognito User Pool&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/identity-pools.html"&gt;Cognito Identity Pool&lt;/a&gt;, and provide less flexibility to the application owner at the end of the day. Read &lt;a href="https://docs.sst.dev/auth#why-not-use-cognito"&gt;here&lt;/a&gt; why the SST team decided to create the new &lt;strong&gt;Auth&lt;/strong&gt; construct.&lt;/p&gt;

&lt;p&gt;The idea behind SST Auth is to provide to the application developer a simple way to implement an authentication system based. It's shipped with adapters for common use cases, like Google, Facebook adapters but you can extend it for your own use case.&lt;/p&gt;

&lt;p&gt;Here we will use it to develop a small application with authentication based on the &lt;a href="https://www.smugmug.com/"&gt;SmugMug&lt;/a&gt; provider. It's specific because SmugMug API is &lt;a href="https://api.smugmug.com/api/v2/doc/tutorial/authorization.html"&gt;supporting only OAuth 1.0a&lt;/a&gt; today, so we will need to build our own custom adapter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build Our Application (step-by-step)
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;br&gt;
This tutorial will use &lt;code&gt;yarn&lt;/code&gt; and &lt;code&gt;TypeScript&lt;/code&gt; as default language (as a classic convention) but feel free to explore the other option offered by SST.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can follow the extensive &lt;a href="https://docs.sst.dev/quick-start"&gt;documentation&lt;/a&gt;, but the first step is to bootstrap our application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn create sst --template=examples/api-sst-auth-google smugmug-auth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This will use the &lt;code&gt;examples/api-sst-auth-google&lt;/code&gt; &lt;a href="https://github.com/serverless-stack/sst/tree/master/examples/api-sst-auth-google"&gt;starter&lt;/a&gt; to step up the coding time. The goal then will be to replace the Google authentication by SmugMug.&lt;/p&gt;

&lt;p&gt;Navigate to your project:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd smugmug-auth/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;First let's add a package to handle OAuth 1.0a authentication. My best bet is today &lt;a href="https://www.npmjs.com/package/oauth"&gt;oauth&lt;/a&gt; which is not new but seems widely used. Unfortunately it doesn't support promises out-of-the-box.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd services/
yarn add oauth
yarn add @types/oauth --dev
yarn add @serverless-stack/node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We can also populate the SmugMug API key and API key secret in a &lt;code&gt;.env.local&lt;/code&gt; file (not pushed to git):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SMUGMUG_CLIENT_ID=your API key
SMUGMUG_CLIENT_SECRET=your API key secret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In order to generate such keys, you need to create a dedicated application in &lt;a href="https://www.smugmug.com/app/developer"&gt;SmugMug Developer&lt;/a&gt; section.&lt;/p&gt;

&lt;p&gt;Then let's start the SST application from the root folder. For this step it's required to be &lt;a href="https://docs.sst.dev/advanced/iam-credentials#loading-from-a-file"&gt;authenticated on an AWS account&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;SST is asking for a stage name. I use generally &lt;code&gt;dev&lt;/code&gt; because I am the only one working on the application, and I deploy the production version on a dedicated &lt;code&gt;prod&lt;/code&gt; stage later on. You can use the stage name you want.&lt;/p&gt;

&lt;p&gt;Once the stack is up you can also start the react app:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd web/
yarn dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And you can try to login on Google:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5B8ybKUs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7giwj1yui40kivkjsjon.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5B8ybKUs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7giwj1yui40kivkjsjon.gif" alt="Login on Google" width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It doesn't work because of the mis-configuration of the application for the deployed backend but basically it's just a configuration point. Let's set up what we need for SmugMug authentication. Here is the flow we want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the user is accessing our application&lt;/li&gt;
&lt;li&gt;the user click on login&lt;/li&gt;
&lt;li&gt;the user is redirected to SmugMug in order to login and accept to share some informations (email, full name) with our application&lt;/li&gt;
&lt;li&gt;then the user is redirected to our application and authenticated.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Backend
&lt;/h3&gt;

&lt;p&gt;Let's start by tweaking the backend. The function &lt;code&gt;services/functions/auth.js&lt;/code&gt; is the heart of the authentication system. It's actually using the &lt;strong&gt;GoogleAdapter&lt;/strong&gt; inside the &lt;strong&gt;AuthHandler&lt;/strong&gt; provided by SST. Unfortunately there is no SmugMugAdapter yet, neither a generic OAuth1.0aAdapter. So let's build it!&lt;/p&gt;

&lt;p&gt;Create a new folder &lt;code&gt;services/functions/smugmug&lt;/code&gt; and a first &lt;code&gt;api.ts&lt;/code&gt; file inside:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;OAuth&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;oauth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.smugmug.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/api/v2`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/services/oauth/1.0a/getRequestToken`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/services/oauth/1.0a/authorize`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accessUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/services/oauth/1.0a/getAccessToken`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signatureMethod&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HMAC-SHA1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;OAuthToken&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;tokenSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SmugMugOAuth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;oauth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OAuth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OAuth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;consumerKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;consumerSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oauth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;OAuth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;requestUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;accessUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;consumerKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;consumerSecret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1.0A&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;signatureMethod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// mandatory to get JSON result on GET and POST method (LIVE API Browser instead!)&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;getOAuthRequestToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;OAuthToken&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oauth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getOAuthRequestToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;oauth_callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;oAuthToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;oAuthTokenSecret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;oAuthToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;tokenSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;oAuthTokenSecret&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nx"&gt;getOAuthAccessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="na"&gt;requestToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OAuthToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;oAuthVerifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;OAuthToken&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oauth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getOAuthAccessToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;requestToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;requestToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tokenSecret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;oAuthVerifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;oAuthAccessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;oAuthAccessTokenSecret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;oAuthAccessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;tokenSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;oAuthAccessTokenSecret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OAuthToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oauth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tokenSecret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;responseData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;responseData&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responseData&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;not a valid answer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This file is creating a &lt;code&gt;SmugMugOAuth&lt;/code&gt; class that is wrapping the &lt;code&gt;oauth&lt;/code&gt; lib for us. It will then be easier to call for the various function provided by &lt;code&gt;oauth&lt;/code&gt;, with promises support! It defines the following methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;getOAuthRequestToken&lt;/code&gt; to get a Request Token (first part of the OAuth exchange)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getOAuthAccessToken&lt;/code&gt; to get an Access Token (second part of the OAuth exchange)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get&lt;/code&gt; to query the SmugMug API with a provided access token&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then in the same folder let's create an &lt;code&gt;adapter.ts&lt;/code&gt; to build our custom &lt;strong&gt;SmugMugAdapter&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;useCookie&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useDomainName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;usePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useQueryParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@serverless-stack/node/api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createAdapter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@serverless-stack/node/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyStructuredResultV2&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-lambda&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;authUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;OAuthToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SmugMugOAuth&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SmugMugUser&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;webUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OAuthToken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SmugMugConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/**
   * The clientId provided by SmugMug
   */&lt;/span&gt;
  &lt;span class="nl"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="cm"&gt;/**
   * The clientSecret provided by SmugMug
   */&lt;/span&gt;
  &lt;span class="nl"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;onSuccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SmugMugUser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;APIGatewayProxyStructuredResultV2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SmugMugAdapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createAdapter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SmugMugConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;oauth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;SmugMugOAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;usePath&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;authorize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Step 1: Obtain a request token&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;useDomainName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;usePath&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;callback&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;oauth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getOAuthRequestToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Step 2: Redirect the user to the authorization URL&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expires&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toUTCString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;302&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="s2"&gt;`req-token=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;requestToken&lt;/span&gt;
          &lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;; HttpOnly; expires=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;expires&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;authUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?oauth_token=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;requestToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;Access=Full&amp;amp;Permissions=Modify`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Step 3: The user logs in to SmugMug (on SmugMug side)&lt;/span&gt;
    &lt;span class="c1"&gt;// The user is presented with a request to authorize your app&lt;/span&gt;

    &lt;span class="c1"&gt;// Step 4: If the user accepts, they will be redirected back to your app, with a verification code embedded in the request&lt;/span&gt;
    &lt;span class="c1"&gt;// Use the verification code to obtain an access token&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;callback&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useQueryParams&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;reqToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OAuthToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;useCookie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;req-token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;oauth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getOAuthAccessToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;reqToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oauth_verifier&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;oauth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/v2!authuser`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onSuccess&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NickName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;webUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WebUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid auth request&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;


&lt;p&gt;The class &lt;code&gt;SmugMugAdapter&lt;/code&gt; is a custom adapter created by the &lt;code&gt;createAdapter&lt;/code&gt; method described in the &lt;a href="https://docs.sst.dev/auth#custom-adapters"&gt;Auth documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It describe all the steps for the OAuth 1.0a process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;when the endpoint &lt;code&gt;authorize&lt;/code&gt; is called, it call the method &lt;code&gt;getOAuthRequestToken&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;then we send the user to the authorization URL on SmugMug with the request token&lt;/li&gt;
&lt;li&gt;SmugMug will redirect the user to the application with a verification code&lt;/li&gt;
&lt;li&gt;With the verification code we can request an access token!&lt;/li&gt;
&lt;li&gt;Finally we are calling the &lt;code&gt;/api/v2!authuser&lt;/code&gt; to retrieve the autenticated user information.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then we need to adapt the &lt;code&gt;services/functions/auth.ts&lt;/code&gt; file to use our custom adapter:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;AuthHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@serverless-stack/node/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Table&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@serverless-stack/node/table&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ViteStaticSite&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@serverless-stack/node/site&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PutItemCommand&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/client-dynamodb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;marshall&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/util-dynamodb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SmugMugAdapter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SmugMugUser&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./smugmug/adapter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kr"&gt;module&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@serverless-stack/node/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SessionTypes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;AuthHandler&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;smugmug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SmugMugAdapter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SMUGMUG_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SMUGMUG_CLIENT_SECRET&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;onSuccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SmugMugUser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ddb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBClient&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ddb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;PutItemCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;TableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;marshall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parameter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IS_LOCAL&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://127.0.0.1:3000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ViteStaticSite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;


&lt;p&gt;Basically we just remove the &lt;strong&gt;GoogleAdapter&lt;/strong&gt; and put instead our &lt;strong&gt;SmugMugAdapter&lt;/strong&gt;! We can also see that we are storing our user in a DynamoDB table (which was already created in the original template). It can be a nice place to store data related to our users.&lt;/p&gt;

&lt;p&gt;Finally pass the environment variables to the &lt;code&gt;auth&lt;/code&gt; Lambda in &lt;code&gt;stacks/MyStack.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// Create Auth provider&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Auth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;authenticator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;functions/auth.handler&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;SMUGMUG_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SMUGMUG_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;SMUGMUG_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SMUGMUG_CLIENT_SECRET&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Frontend
&lt;/h3&gt;

&lt;p&gt;In the frontend we just have to modify the url to redirect the user. Instead of &lt;code&gt;auth/google/authorize/&lt;/code&gt;, it will be &lt;code&gt;auth/smugmug/authorize/&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;web/src/App.jsx&lt;/code&gt;, line 70:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;
        &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_APP_API_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/auth/smugmug/authorize`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"noreferrer"&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;   
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Sign in with SmugMug&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And that's it ; you made it!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cUC9VwNJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4fjzsnifqzd3e5jwmsef.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cUC9VwNJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4fjzsnifqzd3e5jwmsef.gif" alt="First login on SmugMug" width="800" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;session&lt;/code&gt; page is a bit buggy here because we didn't adapt the Google data to the SmugMug data. But you can check that we can retrieve user information in the developer tools:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EJd6Ga5I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4xjboowp6fz2jht3q18f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EJd6Ga5I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4xjboowp6fz2jht3q18f.png" alt="Developer tools" width="800" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the GitHub repository of the final project:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/julbrs"&gt;
        julbrs
      &lt;/a&gt; / &lt;a href="https://github.com/julbrs/sst-smugmug-auth"&gt;
        sst-smugmug-auth
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      How to use SmugMug authentication in a SST project
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1 id="user-content-how-to-add-smugmug-authentication-to-a-serverless-api"&gt;&lt;a class="heading-link" href="https://github.com/julbrs/sst-smugmug-auth#how-to-add-smugmug-authentication-to-a-serverless-api"&gt;How to add SmugMug authentication to a serverless API&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;An example serverless app created with SST, with a custom adapter (SmugMug, OAuth10a support)&lt;/p&gt;
&lt;h2 id="user-content-getting-started"&gt;&lt;a class="heading-link" href="https://github.com/julbrs/sst-smugmug-auth#getting-started"&gt;Getting Started&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://sidoine.org/oauth-with-serverless-using-sst" rel="nofollow"&gt;&lt;strong&gt;Read the tutorial&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="user-content-commands"&gt;&lt;a class="heading-link" href="https://github.com/julbrs/sst-smugmug-auth#commands"&gt;Commands&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="user-content-npm-run-start"&gt;&lt;a class="heading-link" href="https://github.com/julbrs/sst-smugmug-auth#npm-run-start"&gt;&lt;code&gt;npm run start&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Starts the Live Lambda Development environment.&lt;/p&gt;
&lt;h3 id="user-content-npm-run-build"&gt;&lt;a class="heading-link" href="https://github.com/julbrs/sst-smugmug-auth#npm-run-build"&gt;&lt;code&gt;npm run build&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Build your app and synthesize your stacks.&lt;/p&gt;
&lt;h3 id="user-content-npm-run-deploy-stack"&gt;&lt;a class="heading-link" href="https://github.com/julbrs/sst-smugmug-auth#npm-run-deploy-stack"&gt;&lt;code&gt;npm run deploy [stack]&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Deploy all your stacks to AWS. Or optionally deploy, a specific stack.&lt;/p&gt;
&lt;h3 id="user-content-npm-run-remove-stack"&gt;&lt;a class="heading-link" href="https://github.com/julbrs/sst-smugmug-auth#npm-run-remove-stack"&gt;&lt;code&gt;npm run remove [stack]&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Remove all your stacks and all of their resources from AWS. Or optionally removes, a specific stack.&lt;/p&gt;
&lt;h3 id="user-content-npm-run-test"&gt;&lt;a class="heading-link" href="https://github.com/julbrs/sst-smugmug-auth#npm-run-test"&gt;&lt;code&gt;npm run test&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Runs your tests using Jest. Takes all the &lt;a href="https://jestjs.io/docs/en/cli" rel="nofollow"&gt;Jest CLI options&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="user-content-documentation"&gt;&lt;a class="heading-link" href="https://github.com/julbrs/sst-smugmug-auth#documentation"&gt;Documentation&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Learn more about the SST.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.sst.dev/" rel="nofollow"&gt;Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.sst.dev/packages/cli" rel="nofollow"&gt;@serverless-stack/cli&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.sst.dev/packages/resources" rel="nofollow"&gt;@serverless-stack/resources&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;

  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/julbrs/sst-smugmug-auth"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  What we Learned Here?
&lt;/h2&gt;

&lt;p&gt;Starting from a bootstrapped project from SST, we have easily adapted it to support the third-party authentication provider we need with the help of the easily extendable construct provided.&lt;/p&gt;

&lt;p&gt;You can continue the journey by reading the extensive documentation about &lt;a href="https://docs.sst.dev/auth"&gt;Auth&lt;/a&gt;. It's also possible to rely on the &lt;a href="https://docs.sst.dev/auth#oauth"&gt;OAuth&lt;/a&gt; adapter that is supporting out of the box any OAuth2 compatible service.&lt;/p&gt;

&lt;p&gt;Happy authentication!&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>aws</category>
      <category>oauth</category>
      <category>sst</category>
    </item>
    <item>
      <title>Why and How Migrate From Firebase to Serverless Stack?</title>
      <dc:creator>Julien Bras</dc:creator>
      <pubDate>Fri, 25 Mar 2022 03:12:04 +0000</pubDate>
      <link>https://dev.to/julbrs/why-and-how-migrate-from-firebase-to-serverless-stack-40e1</link>
      <guid>https://dev.to/julbrs/why-and-how-migrate-from-firebase-to-serverless-stack-40e1</guid>
      <description>&lt;p&gt;&lt;em&gt;This article is the third of a series around SST - Serverless Stack. I will try to let you discover some amazing aspects of this particular solution in the serverless world. You can find the first  article &lt;a href="https://dev.to/julbrs/sst-the-most-underrated-serverless-framework-you-need-to-discover-25ne"&gt;here (introduction)&lt;/a&gt; and the second one &lt;a href="https://dev.to/julbrs/sst-the-most-underrated-serverless-framework-you-need-to-discover-part-2-jnc"&gt;here (some constructs presentation)&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://firebase.google.com/"&gt;Firebase&lt;/a&gt; is a fantastic tool. It allows you to build mobile or web applications without having to manage a backend by yourself. But somehow, this comes with some drawbacks. In this article I will explain you why you may want to switch, and a practical guide to switch.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://nelligan.sidoine.org/"&gt;a concrete example&lt;/a&gt; I will migrate a &lt;a href="https://reactjs.org/"&gt;React&lt;/a&gt; application that is relying on both Firebase and a &lt;a href="https://www.serverless.com/"&gt;Serverless Framework&lt;/a&gt; backend to a single stack (with &lt;a href="https://serverless-stack.com/"&gt;Serverless Stack&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Short Presentation of Each Solutions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://firebase.google.com/products-build"&gt;&lt;strong&gt;Firebase&lt;/strong&gt;&lt;/a&gt; is a product backed by Google. It allow you to create mobile and web applications based on a set of Firebase components. It contains an &lt;strong&gt;authentication&lt;/strong&gt; layer, a &lt;strong&gt;database&lt;/strong&gt; (FireStore), a &lt;strong&gt;storage&lt;/strong&gt; component to save files, and a &lt;strong&gt;hosting&lt;/strong&gt; solution to ship your application. It’s also possible to rely on &lt;strong&gt;Cloud Function&lt;/strong&gt; to run code in &lt;strong&gt;backend functions&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.serverless.com/"&gt;&lt;strong&gt;Serverless Framework&lt;/strong&gt;&lt;/a&gt; is a solution to host your backend components in a dedicated cloud provider without having to manage servers. For exemple on AWS it will allow your to manage Lambda functions easily.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://serverless-stack.com/"&gt;Serverless Stack&lt;/a&gt;&lt;/strong&gt; is a new solution that can do what Serverless Framework offer. But it offer also to handle the hosting of your web application, and provide a better developer experience in my opinion. I have already written a couple of article on the subject: &lt;a href="https://www.notion.so/SST-The-Most-Underrated-Serverless-Framework-You-Need-to-Discover-63cfa97d720b4199a37c42b56324234e"&gt;here for an introduction&lt;/a&gt; and &lt;a href="https://www.notion.so/SST-The-Most-Underrated-Serverless-Framework-You-Need-to-Discover-part-2-23a504be199b444b9ff14e25d69bf132"&gt;here for some constructs presentation&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://reactjs.org/"&gt;&lt;strong&gt;React&lt;/strong&gt;&lt;/a&gt; is a Javascript library to build user interface 😇&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why You May Want to Migrate?
&lt;/h2&gt;

&lt;p&gt;I was running my system to manage &lt;a href="https://sidoine.org/the-web-is-an-api-scrap-it"&gt;Montreal library cards&lt;/a&gt; since a few year based on &lt;strong&gt;Firebase&lt;/strong&gt;. Because I was using the &lt;a href="https://firebase.google.com/pricing"&gt;free version of Firebase&lt;/a&gt;, I wasn’t able to use &lt;strong&gt;Cloud Functions&lt;/strong&gt;. But to query Montreal library system, it was needed to run some functions somewhere. Back in the days, I have selected &lt;strong&gt;Serverless Framework&lt;/strong&gt; to operate this  backend API on my own AWS account. But it was not ideal, because I was dealing with too much stacks. Focusing on Firebase, here is a list of items that can limit you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Firebase is offering a limited set of functionalities&lt;/strong&gt;: the integrated solutions is providing a really nice set of features for common web application (authentication, storage, database...). But it’s not easily extensible. When you use directly AWS, you can use any service provided by the cloud provider. Think about &lt;em&gt;Machine Learning&lt;/em&gt; service, &lt;em&gt;Queue&lt;/em&gt; systems, &lt;em&gt;Container&lt;/em&gt; workload...&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pricing model is not cheap&lt;/strong&gt;: when you leave the &lt;a href="https://firebase.google.com/pricing"&gt;no-cost plan&lt;/a&gt; (Spark), Firebase can be quite expensive, depending on your usage. For reference this classic article &lt;a href="https://www.dottedsquirrel.com/30k-firebase/"&gt;30k bill on Firebase&lt;/a&gt; is a good reference! The &lt;em&gt;backend-as-a-service&lt;/em&gt; model can lead to such issues if not well optimized. AWS is not cheap either, but you will pay only what you are using and you have more options to build your product (does the frontend is running query on the database directly or via a backend API?)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer experience can be limited&lt;/strong&gt;: local development is a must for serverless application : it reduces the feedback time it take you to test each feature. Firebase offer you a &lt;a href="https://firebase.google.com/docs/emulator-suite"&gt;local emulator suite&lt;/a&gt; to provide you a local environment. It will allow you to test quickly the cloud function built, without waiting them to be shipped. But it’s only an emulation, not real cloud function running on your cloud provider. On the opposite, Serverless Stack is providing you a &lt;a href="https://docs.serverless-stack.com/live-lambda-development"&gt;live lambda development&lt;/a&gt; environment that is relying on AWS services, not emulation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Running the Migration in 6 Steps!
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Init your Serverless Stack application
&lt;/h3&gt;

&lt;p&gt;Following the &lt;a href="https://docs.serverless-stack.com/#quick-start"&gt;quick-start&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a new SST app&lt;/span&gt;
npx create-serverless-stack@latest my-sst-app
&lt;span class="nb"&gt;cd &lt;/span&gt;my-sst-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take some time to explore the organisation of the folder. &lt;code&gt;stacks/&lt;/code&gt; contains your infrastructure setup, &lt;code&gt;src/&lt;/code&gt; will contains your Lambda function code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Migrate from Serverless Framework to the new application
&lt;/h3&gt;

&lt;p&gt;In my specific case, I was migrating functions from Serverless Framework. The guys from SST have a decent documentation for this classic case: &lt;strong&gt;&lt;a href="https://docs.serverless-stack.com/migrating/serverless-framework"&gt;Migrating From Serverless Framework&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Basically I have reused directly the javascript files from the old project, and place them in the &lt;code&gt;src/&lt;/code&gt; folder of the new project. Then inside &lt;code&gt;stacks/MyStack.ts&lt;/code&gt;, I have &lt;a href="https://github.com/julbrs/nelligan-plus/blob/7fcff53b8be57a2505ccbbe1556576c46c02df98/stacks/MyStack.ts"&gt;created my API routes&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Create a HTTP API&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;sst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;defaultAuthorizationType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ApiAuthorizationType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_IAM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET /cards&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;src/cards.list&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST /cards&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;src/cards.add&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DELETE /cards/{id}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;src/cards.remove&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET /cards/{id}/books&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;src/books.list&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;defaultAuthorizationType&lt;/code&gt; allow me to secure the API with an IAM authentication (see next step!).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Replace the Firebase Authentication
&lt;/h3&gt;

&lt;p&gt;Firebase is handy because it comes with an authentication layer built-in. Inside SST the best option is to use the &lt;code&gt;Auth&lt;/code&gt; &lt;a href="https://docs.serverless-stack.com/constructs/Auth"&gt;construct&lt;/a&gt;, that is relying behind the scene on AWS Cognito.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;stacks/MyStack.ts&lt;/code&gt;, I am adding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Create auth&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Auth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;cognito&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;userPoolClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;supportedIdentityProviders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;UserPoolClientIdentityProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;oAuth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;callbackUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`https://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;prodDomainName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:3000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;logoutUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`https://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;prodDomainName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:3000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cognitoUserPool&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
  &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cognitoUserPoolClient&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
  &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_AUTH_CLIENT_ID&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
  &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_AUTH_CLIENT_SECRET&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Please set GOOGLE_AUTH_CLIENT_ID and GOOGLE_AUTH_CLIENT_SECRET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;UserPoolIdentityProviderGoogle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Google&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_AUTH_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_AUTH_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;userPool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cognitoUserPool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;profile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;attributeMapping&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ProviderAttribute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_EMAIL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;givenName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ProviderAttribute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_GIVEN_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;familyName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ProviderAttribute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_FAMILY_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;phoneNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ProviderAttribute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_PHONE_NUMBERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// make sure to create provider before client (https://github.com/aws/aws-cdk/issues/15692#issuecomment-884495678)&lt;/span&gt;
&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cognitoUserPoolClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addDependency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cognitoUserPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addDomain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AuthDomain&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;cognitoDomain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;domainPrefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-nelligan-plus`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Allow authenticated users invoke API&lt;/span&gt;
&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attachPermissionsForAuthUsers&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will allow me the use Google as my principal authentification system (inside &lt;strong&gt;Cognito User Pool&lt;/strong&gt;). There is an alternate way to use Cognito Identity Pool with a simpler declaration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Auth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;google&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;xxx.apps.googleusercontent.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But it’s harder to manage in the React app so I prefer my initial version 😇.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Replace the Firestore Database
&lt;/h3&gt;

&lt;p&gt;The Firebase project rely on Firestore to store some data related to each user. On the new stack you must build a new system to store data. The equivalent structure in AWS world is a &lt;strong&gt;DynamoDB&lt;/strong&gt; table, with a cost per usage. It fits well serverless deployments. There is useful &lt;code&gt;Table&lt;/code&gt; &lt;a href="https://docs.serverless-stack.com/constructs/Table"&gt;construct&lt;/a&gt; available in SST:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Table to store cards&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cards&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;cardId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TableFieldType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;cardUser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TableFieldType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;cardCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TableFieldType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;cardPin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TableFieldType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;primaryIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;partitionKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cardId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: Replace the Firebase Hosting
&lt;/h3&gt;

&lt;p&gt;Here there is multiple approach possible. I am suggesting the most integrated solution for an SST stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use the new &lt;a href="https://docs.serverless-stack.com/constructs/ReactStaticSite"&gt;ReactStaticSite construct&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;take advantage of the &lt;a href="https://docs.serverless-stack.com/packages/static-site-env"&gt;static-site-env&lt;/a&gt; to handle automatically the environment variables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First add in &lt;code&gt;MyStack.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Create frontend app&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reactApp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ReactStaticSite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ReactSite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;buildCommand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;yarn &amp;amp;&amp;amp; yarn build&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;REACT_APP_REGION&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;REACT_APP_API_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="na"&gt;REACT_APP_GA_TRACKING_ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UA-151729273-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;REACT_APP_USER_POOL_ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cognitoUserPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userPoolId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;REACT_APP_USER_POOL_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cognitoUserPoolClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userPoolClientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;REACT_APP_IDENTITY_POOL_ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cognitoIdentityPoolId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;REACT_APP_USER_UI_DOMAIN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domainName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;REACT_APP_DOMAIN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`https://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;prodDomainName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:3000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;customDomain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;domainName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prodDomainName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;hostedZone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sidoine.org&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;environment&lt;/code&gt; props allow to pass environment variables to the React stack. The &lt;code&gt;path&lt;/code&gt; is the relative path that contains your React app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Adapt your React Application
&lt;/h3&gt;

&lt;p&gt;So following step 5, in the &lt;code&gt;react-app/&lt;/code&gt; folder I move my existing React application and start changing it to support my new stack content. Here is a general guidance follow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remove any occurence of &lt;code&gt;firebase&lt;/code&gt; library&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;aws-amplify&lt;/code&gt; instead (it’s a simple wrapper for using AWS ressources like auth, api, etc...)&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;@serverless-stack/static-site-env&lt;/code&gt; to manage environment variable from SST&lt;/li&gt;
&lt;li&gt;Configure &lt;code&gt;aws-amplify&lt;/code&gt; (see example &lt;a href="https://github.com/julbrs/nelligan-plus/blob/7fcff53b8be57a2505ccbbe1556576c46c02df98/react-app/src/amplify.js"&gt;here&lt;/a&gt;, based on environment variables)&lt;/li&gt;
&lt;li&gt;Replace &lt;code&gt;firebase&lt;/code&gt; calls by &lt;code&gt;aws-amplify&lt;/code&gt; calls (that’s probably the most long task!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For reference here is two examples of &lt;code&gt;aws-amplify&lt;/code&gt; usage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;SignIn&lt;/code&gt; &lt;a href="https://github.com/julbrs/nelligan-plus/blob/7fcff53b8be57a2505ccbbe1556576c46c02df98/react-app/src/components/SignIn.js"&gt;component&lt;/a&gt; to sign in the application (rely on &lt;code&gt;CognitoHostedUIIdentityProvider&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Card&lt;/code&gt; &lt;a href="https://github.com/julbrs/nelligan-plus/blob/7fcff53b8be57a2505ccbbe1556576c46c02df98/react-app/src/components/Card.js"&gt;component&lt;/a&gt; that is calling an API endpoint, using the &lt;code&gt;API&lt;/code&gt; object from &lt;code&gt;aws-amplify&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Link to the Project Before and After the Migration
&lt;/h3&gt;

&lt;p&gt;For reference, you can dig into the project before and after the migration:&lt;/p&gt;

&lt;p&gt;Before the migration:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/julbrs/nelligan-plus/tree/sls_firebase"&gt;GitHub - julbrs/nelligan-plus at sls_firebase&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the migration: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/julbrs/nelligan-plus/tree/7fcff53b8be57a2505ccbbe1556576c46c02df98"&gt;GitHub - julbrs/nelligan-plus at 7fcff53b8be57a2505ccbbe1556576c46c02df98&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The switch have been a game-changer for me. And it’s not because of the cost or features, but more for the &lt;strong&gt;developer experience&lt;/strong&gt;. Before the migration, I use to first build the backend function, test it, ship it. Then use this backend function in the frontend application after shipping the backend part. Then maybe I need to go back to the backend to adapt the contract or modify the code... You get it, it was a slow back-and-forth process, not very efficient.&lt;/p&gt;

&lt;p&gt;Today I have a single stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First I start SST via &lt;code&gt;npx sst start&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Then I start my React app locally (&lt;code&gt;yarn start&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I am working on a &lt;strong&gt;development environment&lt;/strong&gt; without link to the production system (thanks to the &lt;a href="https://docs.serverless-stack.com/architecture#stages"&gt;stages&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;I can &lt;strong&gt;change my backend code&lt;/strong&gt; directly in the IDE, and it’s available instantly! Thanks to &lt;a href="https://docs.serverless-stack.com/live-lambda-development"&gt;Live Lambda Development&lt;/a&gt;!&lt;/li&gt;
&lt;li&gt;I don’t have to manage directly the &lt;strong&gt;environment variables of my frontend stack&lt;/strong&gt; (no more &lt;code&gt;.env&lt;/code&gt; file to update!)&lt;/li&gt;
&lt;li&gt;When it’s time to &lt;strong&gt;ship my project&lt;/strong&gt;, just a single command to push both backend and frontend! &lt;code&gt;npx sst deploy --stage prod&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;👉 Get a look at &lt;a href="https://serverless-stack.com/"&gt;Serverless Stack (SST)&lt;/a&gt;, or my example project (&lt;a href="https://github.com/julbrs/nelligan-plus"&gt;here on Github&lt;/a&gt;). It’s really worth the time to understand the main concepts, and then you will be &lt;strong&gt;more efficient building full stack serverless applications!&lt;/strong&gt;&lt;br&gt;
Continue the discussion on &lt;a href="https://twitter.com/_julbrs/status/1505278822429700096"&gt;twitter&lt;/a&gt; 😎&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>firebase</category>
      <category>serverless</category>
      <category>react</category>
      <category>aws</category>
    </item>
    <item>
      <title>SST: The Most Underrated Serverless Framework You Need to Discover (part 2)</title>
      <dc:creator>Julien Bras</dc:creator>
      <pubDate>Sat, 13 Nov 2021 19:35:46 +0000</pubDate>
      <link>https://dev.to/julbrs/sst-the-most-underrated-serverless-framework-you-need-to-discover-part-2-jnc</link>
      <guid>https://dev.to/julbrs/sst-the-most-underrated-serverless-framework-you-need-to-discover-part-2-jnc</guid>
      <description>&lt;p&gt;&lt;em&gt;This article is the second of a series around SST. I will try to let you discover some amazing aspects of this particular solution in the serverless world. You can find the first article &lt;a href="https://dev.to/julbrs/sst-the-most-underrated-serverless-framework-you-need-to-discover-25ne"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So you start building using &lt;strong&gt;serverless&lt;/strong&gt; principles, and you discover the &lt;a href="https://www.serverless.com/" rel="noopener noreferrer"&gt;Serverless framework&lt;/a&gt;. Great ! You will discover here another option, that I consider superior in multiple area, the &lt;a href="https://serverless-stack.com/" rel="noopener noreferrer"&gt;Serverless Stack (SST)&lt;/a&gt;. In this second article, I will focus on some available &lt;em&gt;constructs&lt;/em&gt; after introducing the concept. It can help you to build faster!&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing some concepts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is CDK?
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://serverless-stack.com/" rel="noopener noreferrer"&gt;Serverless Stack (SST)&lt;/a&gt; is based on the &lt;a href="https://aws.amazon.com/cdk/" rel="noopener noreferrer"&gt;AWS Cloud Development Kit (CDK)&lt;/a&gt;. This solution has been introduced by AWS a few years ago, it allow to build &lt;em&gt;infrastructure as code (IaC)&lt;/em&gt; by using a real programming language. If you already know &lt;a href="https://www.terraform.io/" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt;, it is an equivalent product.&lt;/p&gt;

&lt;p&gt;Terraform allow you to declare cloud resources via the &lt;em&gt;HashiCorp Configuration Language (HCL)&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_instance" "iac_in_action" {
  ami               = var.ami_id
  instance_type     = var.instance_type
  availability_zone = var.availability_zone

  // dynamically retrieve SSH Key Name
  key_name = aws_key_pair.iac_in_action.key_name

  // dynamically set Security Group ID (firewall)
  vpc_security_group_ids = [aws_security_group.iac_in_action.id]

  tags = {
    Name = "Terraform-managed EC2 Instance for IaC in Action"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the opposite CDK allow you to declare cloud resources via &lt;em&gt;TypeScript&lt;/em&gt;, &lt;em&gt;Python&lt;/em&gt;, &lt;em&gt;Java&lt;/em&gt; or &lt;em&gt;.Net&lt;/em&gt; (maybe more now?)!&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 ec2 from '@aws-cdk/aws-ec2';
import * as iam from '@aws-cdk/aws-iam';
import * as cdk from '@aws-cdk/core';

export class CdkStarterStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // ... rest

    // 👇 create the EC2 Instance
    const ec2Instance = new ec2.Instance(this, 'ec2-instance', {
      vpc,
      vpcSubnets: {
        subnetType: ec2.SubnetType.PUBLIC,
      },
      role: webserverRole,
      securityGroup: webserverSG,
      instanceType: ec2.InstanceType.of(
        ec2.InstanceClass.T2,
        ec2.InstanceSize.MICRO,
      ),
      machineImage: new ec2.AmazonLinuxImage({
        generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
      }),
      keyName: 'ec2-key-pair',
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(this is a TypeScript example from &lt;a href="https://bobbyhadz.com/blog/aws-cdk-ec2-instance-example" rel="noopener noreferrer"&gt;here&lt;/a&gt;).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A few more differences exists between the solutions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Terraform&lt;/em&gt; can deploy on any supported cloud provider but &lt;em&gt;CDK&lt;/em&gt; is designed to work only on AWS (some tooling exist to break this limit, but it's not the goal of this article!)&lt;/li&gt;
&lt;li&gt;CDK is relying on CloudFormation template and stacks, so the &lt;em&gt;state&lt;/em&gt; of your infrastructure is directly stored in CloudFormation stacks (not in a local json file or a S3 bucket, classic solutions for Terraform)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This will be enough to introduce you the next important concept: &lt;em&gt;constructs&lt;/em&gt;!&lt;/p&gt;

&lt;h3&gt;
  
  
  What are constructs?
&lt;/h3&gt;

&lt;p&gt;From &lt;a href="https://docs.aws.amazon.com/cdk/latest/guide/constructs.html" rel="noopener noreferrer"&gt;AWS Documentation&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Constructs are the basic building blocks of AWS CDK apps. A construct represents a "cloud component" and encapsulates everything AWS CloudFormation needs to create the component.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can think about a set of basic cloud resources are managed behind a construct, and it help you to handle the complexity more easily with a few low number of lines.&lt;/p&gt;

&lt;p&gt;For example &lt;a href="https://awscdk.io/packages/@aws-cdk/aws-ecs-patterns@1.95.1/#/" rel="noopener noreferrer"&gt;here&lt;/a&gt; we can see how it's possible to use the construct &lt;code&gt;ApplicationLoadBalancedFargateService&lt;/code&gt; in a few lines of codes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', {
  cluster,
  memoryLimitMiB: 1024,
  cpu: 512,
  taskImageOptions: {
    image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This single object is responsible to create the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a ECS task definition&lt;/li&gt;
&lt;li&gt;a ECS service based on Fargate&lt;/li&gt;
&lt;li&gt;an Application Load Balancer &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And it can also generate for you a ECS Cluster if you provide a VPC. So in just &lt;strong&gt;8&lt;/strong&gt; lines you create not less than 4 or 5 AWS resources! &lt;/p&gt;

&lt;h2&gt;
  
  
  Constructs Provided By SST
&lt;/h2&gt;

&lt;p&gt;As said previously SST is based on CDK so it use intrinsically all CDK concepts, including &lt;em&gt;Constructs&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;One example provided in the first &lt;a href="https://dev.to/julbrs/sst-the-most-underrated-serverless-framework-you-need-to-discover-25ne"&gt;article&lt;/a&gt; was using the &lt;code&gt;Api&lt;/code&gt; object, it's probably the most basic SST Construct available! Let's get a view on the most important constructs available in the framework and understand how it can help you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FLGah2Lj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FLGah2Lj.png" alt="How to find constructs on SST documentation!"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(&lt;em&gt;Go &lt;a href="https://docs.serverless-stack.com/" rel="noopener noreferrer"&gt;here&lt;/a&gt; to get the full list!&lt;/em&gt;)&lt;/p&gt;

&lt;h3&gt;
  
  
  Api
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const api = new Api(this, "Api", {
  routes: {
    "GET  /notes": "src/list.main",
    "POST /notes": "src/create.main",
  },
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most simple one, to declare API HTTP endpoints very quickly. It mimics what can be done in the classic &lt;em&gt;Serverless Framework&lt;/em&gt;. See my &lt;a href="https://dev.to/julbrs/sst-the-most-underrated-serverless-framework-you-need-to-discover-25ne"&gt;first article&lt;/a&gt; for a more in-depth comparison.&lt;/p&gt;

&lt;h3&gt;
  
  
  Auth
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Auth } from "@serverless-stack/resources";

const auth = new Auth(this, "Auth", {
  cognito: true,
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Auth&lt;/em&gt; will allow you to manage user authentication on your application, API... It's based on AWS services: &lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools.html" rel="noopener noreferrer"&gt;Cognito User Pool&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-identity.html" rel="noopener noreferrer"&gt;Cognito Identity Pool&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can then manage the permissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;auth.attachPermissionsForAuthUsers([
  api,
]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Authenticated user of the &lt;code&gt;auth&lt;/code&gt; object will be allowed to access the API endpoints declared by the &lt;code&gt;api&lt;/code&gt; object (you need to &lt;a href="https://docs.serverless-stack.com/constructs/Api#adding-auth" rel="noopener noreferrer"&gt;secure&lt;/a&gt; your API for that, by default API is not secured and everyone can use it).&lt;/p&gt;

&lt;h3&gt;
  
  
  Table
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://docs.serverless-stack.com/constructs/Table" rel="noopener noreferrer"&gt;Table&lt;/a&gt; construct will handle for you a DynamoDB table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Table, TableFieldType } from "@serverless-stack/resources";

const table = new Table(this, "Notes", {
  fields: {
    userId: TableFieldType.STRING,
    noteId: TableFieldType.STRING,
  },
  primaryIndex: { partitionKey: "noteId", sortKey: "userId" },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then if you want to allow the Lambda functions inside your &lt;code&gt;api&lt;/code&gt; object to be able to read / write in the table you have to specify that with the &lt;code&gt;permissions&lt;/code&gt; prop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;new Api(this, "Api", {
  defaultFunctionProps: {
    permissions: [table],
  },
  routes: {
    "GET  /notes": "src/list.main",
    "POST /notes": "src/create.main",
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ReactStaticSite
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.serverless-stack.com/constructs/ReactStaticSite" rel="noopener noreferrer"&gt;ReactStaticSite&lt;/a&gt; is a very cleaver constructs that will allow you to include your frontend application inside the SST application!&lt;/p&gt;

&lt;p&gt;Let's imagine you have a &lt;code&gt;frontend/&lt;/code&gt; folder inside the SST application with a classic &lt;em&gt;ReactJs&lt;/em&gt; application. Add the following to your SST stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const site = new ReactStaticSite(this, "ReactSite", {
  path: "frontend/",
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This 3-lines of code will do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create for you a &lt;strong&gt;S3 bucket&lt;/strong&gt; to host the frontend files&lt;/li&gt;
&lt;li&gt;create for you a &lt;strong&gt;CloudFront deployment&lt;/strong&gt; to manage a CDN for your frontend&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;build&lt;/strong&gt; your ReactJs application when you are deploying the application (&lt;code&gt;sst deploy&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;push to S3&lt;/strong&gt; the build version of your ReactJs application to the S3 bucket dedicated&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;invalidate&lt;/strong&gt; the CloudFront deployment (to get ride of existing cached data on the CDN)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And you can also imagine the power behind the &lt;a href="https://docs.serverless-stack.com/constructs/StaticSite" rel="noopener noreferrer"&gt;StaticSite&lt;/a&gt; or the &lt;a href="https://docs.serverless-stack.com/constructs/NextjsSite" rel="noopener noreferrer"&gt;NextjsSite&lt;/a&gt; constructs 😅&lt;/p&gt;

&lt;h3&gt;
  
  
  Handle Environment Variables with ReactStaticSite
&lt;/h3&gt;

&lt;p&gt;Generally you will use some &lt;code&gt;REACT_APP_*&lt;/code&gt; variables in your ReactJs application in order to share the URL endpoint of your API, the Cognito User Pool Id, etc... A classic way to do that is to create a &lt;code&gt;.env&lt;/code&gt; file at the root level of your ReactJs application (&lt;a href="https://create-react-app.dev/docs/adding-custom-environment-variables/#adding-development-environment-variables-in-env" rel="noopener noreferrer"&gt;here&lt;/a&gt; for Create React App documentation)&lt;/p&gt;

&lt;p&gt;SST is providing a very efficient way to handle that without messing with a &lt;code&gt;.env&lt;/code&gt; file anymore! First define the variable in the &lt;code&gt;ReactStaticSite&lt;/code&gt; construct like that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;new ReactStaticSite(this, "ReactSite", {
  path: "path/to/src",
  environment: {
    REACT_APP_API_URL: api.url,
    REACT_APP_USER_POOL_CLIENT: auth.cognitoUserPoolClient.userPoolClientId,
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in combination with the &lt;a href="https://docs.serverless-stack.com/live-lambda-development" rel="noopener noreferrer"&gt;Live Lambda Development&lt;/a&gt; environment (it will be my next article!) you will be able to start your local React application by using the SST live information!&lt;/p&gt;

&lt;p&gt;Add the following package to your ReactJs application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install --save-dev @serverless-stack/static-site-env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And change the &lt;code&gt;start&lt;/code&gt; script in &lt;code&gt;package.json&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;"scripts": {
  "start": "sst-env -- react-scripts start",
  "build": "react-scripts build",
  "test": "react-scripts test",
  "eject": "react-scripts eject"
},
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More on this feature in the &lt;a href="https://docs.serverless-stack.com/constructs/ReactStaticSite#configuring-environment-variables" rel="noopener noreferrer"&gt;dedicated documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This is the end of this second article about SST framework. I hope you understand better the power behind each &lt;em&gt;construct&lt;/em&gt; provided by the framework. The team behind is very active and ship new features very often. I remember a few months ago, saying to myself: "if only some kind of static site could be embed into SST it will be just &lt;em&gt;magic&lt;/em&gt;". Then I see &lt;a href="https://github.com/serverless-stack/serverless-stack/releases/tag/v0.33.0" rel="noopener noreferrer"&gt;v0.33.0&lt;/a&gt; 😱.&lt;/p&gt;

&lt;p&gt;Just give a look at the follow resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.serverless-stack.com/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; which is top notch&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://serverless-stack.com/#guide" rel="noopener noreferrer"&gt;guide&lt;/a&gt; that contains a lot of examples, and some comparison with Serverless Framework&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the next episode of this series, I will speak about the local development feature of this framework, you can &lt;a href="https://dev.to/julbrs"&gt;follow me&lt;/a&gt; to be informed when it will be out!&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>aws</category>
      <category>cdk</category>
      <category>sst</category>
    </item>
    <item>
      <title>SST: The Most Underrated Serverless Framework You Need to Discover</title>
      <dc:creator>Julien Bras</dc:creator>
      <pubDate>Sun, 26 Sep 2021 22:11:25 +0000</pubDate>
      <link>https://dev.to/julbrs/sst-the-most-underrated-serverless-framework-you-need-to-discover-25ne</link>
      <guid>https://dev.to/julbrs/sst-the-most-underrated-serverless-framework-you-need-to-discover-25ne</guid>
      <description>&lt;p&gt;&lt;em&gt;This article will be the first of a series around SST. I will try to let you discover some amazing aspect of this particular solution in the serverless world.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So you start building using &lt;strong&gt;serverless&lt;/strong&gt; principles, and you discover the &lt;a href="https://www.serverless.com/" rel="noopener noreferrer"&gt;Serverless framework&lt;/a&gt;. Great ! You will discover here another option, that I consider superior in multiple area, the &lt;a href="https://serverless-stack.com/" rel="noopener noreferrer"&gt;Serverless Stack (SST)&lt;/a&gt;. I will first introduce some basic concepts for beginners, then I will expose the main difference between the solutions. Let's dig into this!&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing some concepts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Serverless
&lt;/h3&gt;

&lt;p&gt;Serverless is "is a cloud computing execution model in which the cloud provider allocates machine resources on demand, taking care of the servers on behalf of their customers" (&lt;a href="https://en.wikipedia.org/wiki/Serverless_computing" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt;). It allow you to run backend functions or applications without managing any backend server, or containers. Each major cloud provider is providing today serverless solutions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Lambda&lt;/em&gt; on AWS&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Microsoft Azure Functions&lt;/em&gt; on Azure&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Cloud Functions&lt;/em&gt; on Google Cloud Platform&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basically you just define a function in a supported language, you push it to your cloud provider and &lt;em&gt;voilà!&lt;/em&gt;, it's accessible and you don't have to deal with servers. It's the developer dream!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F5HgAtm2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F5HgAtm2.png" alt="AWS Lambda"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A very simple serverless function on AWS!&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Framework for Serverless
&lt;/h3&gt;

&lt;p&gt;Once you have build your first functions using serverless, you discover that the feedback loop to test something is generally longer that your local code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First you develop the function&lt;/li&gt;
&lt;li&gt;You need to deploy it on your cloud provider (manually maybe the first time)&lt;/li&gt;
&lt;li&gt;You need to wait for the deployment, attach HTTP trigger if it's a REST API for example (API Gateway for AWS)&lt;/li&gt;
&lt;li&gt;You test it (via Postman or using your web-application maybe)&lt;/li&gt;
&lt;li&gt;You detected a bug &lt;/li&gt;
&lt;li&gt;You get the log trace in the log system of the cloud provider (CloudWatch for AWS for example)&lt;/li&gt;
&lt;li&gt;Then your go back to the start of the list&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The back-and-forth is generally more costly because the code cannot be run directly on your local machine. So we try to rely on a solution that will speed up the development process, and eventually allow us to develop locally before pushing to the cloud provider (again to speed-up the process).&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the Serverless Framework?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F6SATV9x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F6SATV9x.png" alt="Image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.serverless.com/" rel="noopener noreferrer"&gt;This&lt;/a&gt; particular framework was introduced back in 2015 (under the name JAWS, source: &lt;a href="https://en.wikipedia.org/wiki/Serverless_Framework" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt;). The project have an astonishing &lt;strong&gt;40k&lt;/strong&gt; stars on &lt;a href="https://github.com/serverless/serverless" rel="noopener noreferrer"&gt;Github&lt;/a&gt;! This framework offer a high number of features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it support a large number of languages (more or less the same as supported language for serverless platform)&lt;/li&gt;
&lt;li&gt;it is compatible with the major cloud provider here (AWS, Azure, GCP...)&lt;/li&gt;
&lt;li&gt;it allow you to develop locally before pushing to the cloud provider&lt;/li&gt;
&lt;li&gt;there is &lt;a href="https://www.serverless.com/plugins/" rel="noopener noreferrer"&gt;a million plugins&lt;/a&gt; that can help you to achieve what you need&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main piece in the framework is the &lt;code&gt;serverless.yml&lt;/code&gt; file that is the configuration file of your serverless application. You can list here all the functions you want to deploy, how they are triggered (http endpoint or cron scheduling for example).&lt;/p&gt;

&lt;p&gt;Here is an example file (from &lt;a href="https://github.com/serverless/examples/blob/master/aws-node-rest-api/serverless.yml" rel="noopener noreferrer"&gt;serverless/examples repo&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;service: aws-node-rest-api

frameworkVersion: '2'


provider:
  name: aws
  runtime: nodejs12.x
  lambdaHashingVersion: '20201221'

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: /
          method: get
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It describes in a very efficient way the provider and runtime you want to use, then the list of function you want to deploy. Each function is referenced with a &lt;code&gt;handler&lt;/code&gt;. For &lt;em&gt;nodejs&lt;/em&gt; runtime, it's just pointing to the file named &lt;code&gt;handler.js&lt;/code&gt; and the exported function named &lt;code&gt;hello&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then you add &lt;code&gt;events&lt;/code&gt; for each function if you want to trigger the function on HTTP event for example.&lt;/p&gt;

&lt;p&gt;Under the hood the deployment is managed by CloudFormation on AWS (I do not have any experience on deployment on different cloud provider). It's possible to add custom resources in the &lt;code&gt;serverless.yml&lt;/code&gt; file using &lt;a href="https://www.serverless.com/framework/docs/providers/aws/guide/resources" rel="noopener noreferrer"&gt;CloudFormation template language&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  My Story With This Framework
&lt;/h3&gt;

&lt;p&gt;I have discover this framework back in 2018/2019, in my early day playing with Lambda on AWS. I think it is still the first thing you are testing after playing with serverless manually.&lt;/p&gt;

&lt;p&gt;I use it to build some simple scheduling jobs on AWS, and build some full featured REST API.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the Serverless Stack (SST)?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FQyFBw4r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FQyFBw4r.png" alt="Image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://serverless-stack.com/" rel="noopener noreferrer"&gt;This&lt;/a&gt; framework is much more recent. There is a couple of post here on &lt;strong&gt;DEV&lt;/strong&gt; from &lt;a href="https://dev.to/fwang"&gt;Franck Wang&lt;/a&gt;, the author of this framework starting on September 2020 (&lt;a href="https://dev.to/aws-builders/using-serverless-framework-and-cdk-together-12he"&gt;here&lt;/a&gt; and &lt;a href="https://dev.to/aws-builders/work-on-your-lambda-functions-live-51cp"&gt;here&lt;/a&gt;). The project have only &lt;strong&gt;3k&lt;/strong&gt; stars on &lt;a href="https://github.com/serverless-stack/serverless-stack" rel="noopener noreferrer"&gt;Github&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The framework is much more limited in term of features, as it's new, but it cover the most classic use-cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;only AWS is supported as cloud provider&lt;/li&gt;
&lt;li&gt;only &lt;a href="https://docs.serverless-stack.com/installation" rel="noopener noreferrer"&gt;a few languages are supported&lt;/a&gt; for Lambda functions (no &lt;em&gt;Java&lt;/em&gt; for example)&lt;/li&gt;
&lt;li&gt;a local setup is available for development&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The framework is relying on &lt;a href="https://aws.amazon.com/cdk/" rel="noopener noreferrer"&gt;CDK&lt;/a&gt; which is an AWS tool designed to "define your cloud application resources using familiar programming languages". Behind the scene CDK is allowing you to write code that will be translated to CloudFormation template files. It's generally much more compact to write CDK that the equivalent CloudFormation template file!&lt;/p&gt;

&lt;p&gt;A simple example of the framework:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;new Api(this, "Api", {
  routes: {
    "GET  /notes": "src/list.main",
    "POST /notes": "src/create.main",
  },
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This piece of code is using &lt;code&gt;Api&lt;/code&gt; which is a &lt;em&gt;Construct&lt;/em&gt;. A &lt;em&gt;Construct&lt;/em&gt; is a CDK concept that represent a set of pre-configured resources on AWS. It's a very efficient way to define quickly an application. Same as the previous framework here I define HTTP routes. The first one &lt;code&gt;GET /notes&lt;/code&gt; will use a Lambda function with the code available in the file &lt;code&gt;src/list.js&lt;/code&gt; under the exported function &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  My Story With This Framework
&lt;/h3&gt;

&lt;p&gt;I have discover the framework begin of 2021 and it's blowing my mind each time I am using it. I think that CDK is far superior to CloudFormation template files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can split your code in multiple file easily!&lt;/li&gt;
&lt;li&gt;Your IDE is generally helping you writing CDK (TypeScript assisted for example)&lt;/li&gt;
&lt;li&gt;You are writing less (Construct are helping to handle high order components !)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use it actually to build a complete application (front + back) with authentication, relying a lot on &lt;em&gt;Construct&lt;/em&gt; provided by SST team (&lt;a href="https://docs.serverless-stack.com/constructs/Auth" rel="noopener noreferrer"&gt;Auth&lt;/a&gt;, &lt;a href="https://docs.serverless-stack.com/constructs/Cron" rel="noopener noreferrer"&gt;Cron&lt;/a&gt;, &lt;a href="https://docs.serverless-stack.com/constructs/Bucket" rel="noopener noreferrer"&gt;Bucket&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This is the end of my first article on this framework. I hope it will help you give a try when you will have to choose a serverless Framework! In the next episode of this series, I will speak about the local development feature of this framework, you can &lt;a href="https://dev.to/julbrs"&gt;follow me&lt;/a&gt; to be informed when it will be out!&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>aws</category>
      <category>cdk</category>
      <category>sst</category>
    </item>
    <item>
      <title>How to Run a Minecraft Server on AWS For Less Than 3 US$ a Month</title>
      <dc:creator>Julien Bras</dc:creator>
      <pubDate>Sat, 07 Aug 2021 21:58:49 +0000</pubDate>
      <link>https://dev.to/julbrs/how-to-run-a-minecraft-server-on-aws-for-less-than-3-us-a-month-409p</link>
      <guid>https://dev.to/julbrs/how-to-run-a-minecraft-server-on-aws-for-less-than-3-us-a-month-409p</guid>
      <description>&lt;p&gt;During the first weeks of the COVID-19 pandemic, back in april 2020 my son ask me to build a Minecraft server in order to play on the same world with his school friend. After checking &lt;a href="https://clovux.net/mc/" rel="noopener noreferrer"&gt;some available services&lt;/a&gt; (yeah not so expensive finally), I have chosen to build a server on a EC2 instance. This article will explain you how to optimize the cost 😜, based on the usage!&lt;/p&gt;

&lt;h2&gt;
  
  
  Some Tools Used in the Article
&lt;/h2&gt;

&lt;h3&gt;
  
  
  AWS
&lt;/h3&gt;

&lt;p&gt;I want to rely only on AWS services as I want to increase my knowledge on this big cloud offering. There is always one service you don't know ! In this particular example I will use the following services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/ec2/" rel="noopener noreferrer"&gt;EC2&lt;/a&gt; (virtual servers in the cloud)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/lambda/" rel="noopener noreferrer"&gt;Lambda&lt;/a&gt; (serverless functions)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/ses/" rel="noopener noreferrer"&gt;Simple Email Service&lt;/a&gt; (Email Sending and Receiving Service)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Minecraft
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.minecraft.net/" rel="noopener noreferrer"&gt;Minecraft&lt;/a&gt; is a popular sandbox video-game. In this case I will focus on the Minecraft &lt;em&gt;Java Edition&lt;/em&gt;, because the server version is running well on Linux server, and my son is running a laptop on Debian.&lt;/p&gt;

&lt;h2&gt;
  
  
  Global Architecture of the Solution
&lt;/h2&gt;

&lt;p&gt;The first month operating the server, I noticed that my son is using it a couple of hours each day, and then the server was idle. It's built on a EC2 &lt;code&gt;t2.small&lt;/code&gt; with a 8 GB disk so I have a monthly cost of about &lt;strong&gt;18 US$&lt;/strong&gt;. Not a lot but I was thinking that there is room for improvement! The main part of the cost is the EC2 compute cost (~17 US$) and I know that it's not used 100% of the time. The global idea is to &lt;strong&gt;start the server only when my son is using it&lt;/strong&gt;, but he doesn't have access to my AWS Console so I need to find a sweet solution!&lt;/p&gt;

&lt;p&gt;Here is the various blocks used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an &lt;strong&gt;EC2 instance&lt;/strong&gt;, the Minecraft server&lt;/li&gt;
&lt;li&gt;use &lt;strong&gt;SES&lt;/strong&gt; (Simple Email Service) to receive e-mail, and trigger a Lambda function&lt;/li&gt;
&lt;li&gt;one &lt;strong&gt;Lambda&lt;/strong&gt; function to start the server&lt;/li&gt;
&lt;li&gt;one &lt;strong&gt;Lambda&lt;/strong&gt; function to stop the server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And that's it. My son is using it this way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;send an e-mail to a specific and secret e-mail address, this will start the instance&lt;/li&gt;
&lt;li&gt;after 8h the instance is shutdown by the lambda function (I estimate that my son must not play on Minecraft more than 8h straight 😅)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Let's Build it Together
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Build the EC2 Instance
&lt;/h3&gt;

&lt;p&gt;This is the initial part, you must create a new EC2 instance. From the EC2 dashboard, click on  &lt;code&gt;Launch Instance&lt;/code&gt; and choose the &lt;em&gt;Amazon Linux 2 AMI&lt;/em&gt; with the &lt;em&gt;x86&lt;/em&gt; option.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimgur.com%2FuzpQFuQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimgur.com%2FuzpQFuQ.png" alt="Launch Instance"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next you must choose the &lt;em&gt;Instance Type&lt;/em&gt;. I recommend you the &lt;code&gt;t2.small&lt;/code&gt; for Minecraft. You will able to change it after the creation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimgur.com%2FIcbZbLL.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimgur.com%2FIcbZbLL.png" alt="Choose Instance Type"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on &lt;code&gt;Next: Configure Instance Details&lt;/code&gt; to continue the configuration. Keep the default settings, and the default size for the disk (8 GB) as it's enough.&lt;/p&gt;

&lt;p&gt;For the tag screen I generally provide a &lt;code&gt;Name&lt;/code&gt; (it's then displayed on EC2 instance list) and a &lt;code&gt;costcenter&lt;/code&gt; (I use it for cost management later).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimgur.com%2FOt8kHNX.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimgur.com%2FOt8kHNX.png" alt="Instance Tags"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the Security Group, it the equivalent of a firewall on EC2 and you must configure which port will be accessible from internet on your server. I add SSH port and the Minecraft port (25565) like you see on the following screen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimgur.com%2FRFvfmWR.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimgur.com%2FRFvfmWR.png" alt="Instance Security Group"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then to start the instance you must select or create a key pair. It's mandatory and allow then to connect remotely to your EC2 instance. In my case I am using an existing key pair but if you create a new key don't forget to download on your laptop the &lt;em&gt;private key file&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimgur.com%2F0ptipKZ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimgur.com%2F0ptipKZ.png" alt="Instance Key"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Yes my key is named caroline. Why not?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Then you must connect your instance via SSH, I recommend this &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AccessingInstances.html" rel="noopener noreferrer"&gt;guide&lt;/a&gt; if you need help. Basically you must run this kind of command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

ssh -i my_private_key.pem ec2-user@public-ipv4


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

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;public-ipv4&lt;/code&gt; is available in the instance list:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimgur.com%2FyEmuRw9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimgur.com%2FyEmuRw9.png" alt="Get the IPv4 address"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You first need java. As newer build of minecraft (since 1.17) are running only on Java 17, I recommend to use Corretto (the Amazon Java version):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

sudo rpm --import https://yum.corretto.aws/corretto.key
sudo curl -L -o /etc/yum.repos.d/corretto.repo https://yum.corretto.aws/corretto.repo
sudo yum install -y java-17-amazon-corretto-devel.x86_64
java --version


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

&lt;/div&gt;

&lt;p&gt;You must have something like: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

openjdk 17.0.1 2021-10-19 LTS
OpenJDK Runtime Environment Corretto-17.0.1.12.1 (build 17.0.1+12-LTS)
OpenJDK 64-Bit Server VM Corretto-17.0.1.12.1 (build 17.0.1+12-LTS, mixed mode, sharing)


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

&lt;/div&gt;

&lt;p&gt;Thanks &lt;a class="mentioned-user" href="https://dev.to/mudhen459"&gt;@mudhen459&lt;/a&gt; for the research on this java issue ;) &lt;/p&gt;

&lt;p&gt;And I want a dedicated user:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

sudo adduser minecraft


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

&lt;/div&gt;

&lt;p&gt;To install Minecraft you can rely on the Minecraft server page &lt;a href="https://www.minecraft.net/en-us/download/server" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For example for the version &lt;code&gt;1.17.1&lt;/code&gt; I can run the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

sudo su
mkdir /opt/minecraft/
mkdir /opt/minecraft/server/
cd /opt/minecraft/server
wget https://launcher.mojang.com/v1/objects/a16d67e5807f57fc4e550299cf20226194497dc2/server.jar
sudo chown -R minecraft:minecraft /opt/minecraft/


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

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ Warning regarding Java version:&lt;/strong&gt;&lt;br&gt;
It seems that starting with Minecraft 1.17, it require now a Java JRE 16 (instead of Java JRE 8).&lt;br&gt;
&lt;a href="https://mcversions.net/download/1.16.5" rel="noopener noreferrer"&gt;This site&lt;/a&gt; is giving you links to download older Minecraft versions if needed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

Exception in thread "main" java.lang.UnsupportedClassVersionError: net/minecraft/server/Main has been compiled by a more recent version of the Java Runtime (class file version 60.0), this version of the Java Runtime only recognizes class file versions up to 52.0


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

&lt;/div&gt;

&lt;p&gt;I have created a little service to avoid start manually the server. I want the Minecraft process to start as soon as I start the server.&lt;/p&gt;

&lt;p&gt;To do that I have created a file under &lt;code&gt;/etc/systemd/system/minecraft.service&lt;/code&gt; with the following content:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

[Unit]
Description=Minecraft Server
After=network.target

[Service]
User=minecraft
Nice=5
KillMode=none
SuccessExitStatus=0 1
InaccessibleDirectories=/root /sys /srv /media -/lost+found
NoNewPrivileges=true
WorkingDirectory=/opt/minecraft/server
ReadWriteDirectories=/opt/minecraft/server
ExecStart=/usr/bin/java -Xmx1024M -Xms1024M -jar server.jar nogui
ExecStop=/opt/minecraft/tools/mcrcon/mcrcon -H 127.0.0.1 -P 25575 -p strong-password stop

[Install]
WantedBy=multi-user.target


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

&lt;/div&gt;

&lt;p&gt;Then advise the new service by the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

chmod 664 /etc/systemd/system/minecraft.service
systemctl daemon-reload


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

&lt;/div&gt;

&lt;p&gt;More information on systemd &lt;a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/system_administrators_guide/chap-managing_services_with_systemd#sect-Managing_Services_with_systemd-Unit_Files" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Now if you restart the EC2 instance a Minecraft server must be available! You can check ✅ this first step!&lt;/p&gt;

&lt;p&gt;I am not speaking of the fact that the IPv4 is dynamic by default. I recommend to setup an static &lt;code&gt;Elastic IP&lt;/code&gt; for this server (&lt;a href="https://aws.amazon.com/premiumsupport/knowledge-center/ec2-associate-static-public-ip/" rel="noopener noreferrer"&gt;here!&lt;/a&gt;) in order to get a static IP.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Build the Start Scenario
&lt;/h3&gt;

&lt;p&gt;Let's first create our Lambda function. Go into &lt;strong&gt;Lambda&lt;/strong&gt;, and click on &lt;code&gt;Create function&lt;/code&gt; to build a new one. Name it &lt;code&gt;mc_start&lt;/code&gt; and use a &lt;code&gt;Node.js 14.x&lt;/code&gt; or more runtime.&lt;/p&gt;

&lt;p&gt;Then you must have this type of screen: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimgur.com%2FVfZiKIL.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimgur.com%2FVfZiKIL.png" alt="Lambda Start"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Replace the content of &lt;code&gt;index.js&lt;/code&gt; file with the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

const AWS = require("aws-sdk");
var ec2 = new AWS.EC2();

exports.handler = async (event) =&amp;gt; {
  try {
    var result;
    var params = {
      InstanceIds: [process.env.INSTANCE_ID],
    };
    var data = await ec2.startInstances(params).promise();
    result = "instance started"

    const response = {
      statusCode: 200,
      body: result,
    };
    return response;
  } catch (error) {
    console.error(error);
    const response = {
      statusCode: 500,
      body: "error during script",
    };
    return response;
  }
};


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

&lt;/div&gt;

&lt;p&gt;In Configuration, set the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add an environnement variable named &lt;code&gt;INSTANCE_ID&lt;/code&gt; with the value that correspond to the Instance Id of your Minecraft server (something like &lt;code&gt;i-031fdf9c3bafd7a34&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;the role permissions must include the right to start our EC2 instance like this:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimgur.com%2FPBgQol9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimgur.com%2FPBgQol9.png" alt="Lambda Permissions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In Simple Email Service, it's time to create a new &lt;em&gt;Rule Set&lt;/em&gt; in the &lt;code&gt;Email Receiving&lt;/code&gt; section:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimgur.com%2FRvMZdRu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimgur.com%2FRvMZdRu.png" alt="SES EMail Receiving"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on &lt;code&gt;Create rule&lt;/code&gt; inside &lt;code&gt;default-rule-set&lt;/code&gt;. Take note that the Email Receiving feature is only available today in 3 regions: &lt;code&gt;us-east-1&lt;/code&gt;, &lt;code&gt;us-west-2&lt;/code&gt; and &lt;code&gt;eu-west-1&lt;/code&gt; (source &lt;a href="https://docs.aws.amazon.com/ses/latest/DeveloperGuide/receiving-email.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;If SES is receiving an email on this particular identity:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimgur.com%2F6z0F1LZ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimgur.com%2F6z0F1LZ.png" alt="SES Config 1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It invoke a Lambda function:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimgur.com%2FBLDLgPR.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimgur.com%2FBLDLgPR.png" alt="SES Config 2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You must &lt;a href="https://docs.aws.amazon.com/ses/latest/dg/verify-addresses-and-domains.html" rel="noopener noreferrer"&gt;add the domain&lt;/a&gt; to the &lt;code&gt;Verified identities&lt;/code&gt; to make this work. It's also necessary to publish an MX entry in order to declare SES as the email receiver for a specific domain or subdomain (more info &lt;a href="https://docs.aws.amazon.com/ses/latest/dg/receiving-email-setting-up.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Build the Stop Scenario
&lt;/h3&gt;

&lt;p&gt;This time we want to stop the instance after 8h. It's a simple Lambda function.&lt;/p&gt;

&lt;p&gt;Let's first create our Lambda function. Go into &lt;strong&gt;Lambda&lt;/strong&gt;, and click on &lt;code&gt;Create function&lt;/code&gt; to build a new one. Name it &lt;code&gt;mc_shutdown&lt;/code&gt; and use a &lt;code&gt;Node.js 14.x&lt;/code&gt; or more runtime.&lt;/p&gt;

&lt;p&gt;Replace the content of &lt;code&gt;index.js&lt;/code&gt; file with the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

const AWS = require("aws-sdk");
var ec2 = new AWS.EC2();

exports.handler = async (event) =&amp;gt; {
  try {
    var result;
    var params = {
      InstanceIds: [process.env.INSTANCE_ID],
    };
    var data = await ec2.describeInstances(params).promise();
    var instance = data.Reservations[0].Instances[0];

    if (instance.State.Name !== "stopped") {
      var launch_time = new Date(instance.LaunchTime);
      var today = new Date();
      result = "instance running";
      if ((today - launch_time) / 3600000 &amp;gt; process.env.MAX_HOURS) {
        console.log("stopping the instance...");
        var stop_data = await ec2.stopInstances(params).promise();
        result = "instance stopped";
      }
    } else {
      result = "instance not running";
    }
    const response = {
      statusCode: 200,
      body: result,
    };
    return response;
  } catch (error) {
    console.error(error);
    const response = {
      statusCode: 500,
      body: "error during script",
    };
    return response;
  }
};



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

&lt;/div&gt;

&lt;p&gt;In Configuration, set the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add an environnement variable named &lt;code&gt;INSTANCE_ID&lt;/code&gt; with the value that correspond to the Instance Id of your Minecraft server (something like &lt;code&gt;i-031fdf9c3bafd7a34&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;add an environnement variable named &lt;code&gt;MAX_HOURS&lt;/code&gt; with the value that correspond to number of hours allowed after startup, like &lt;code&gt;8&lt;/code&gt; for 8 hours).&lt;/li&gt;
&lt;li&gt;the role permissions must include the right to start our EC2 instance like this:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimgur.com%2FXykehhf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimgur.com%2FXykehhf.png" alt="Lambda Permissions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We add a trigger to fire the task every 20 minutes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimgur.com%2FKM84uEU.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimgur.com%2FKM84uEU.png" alt="Add Trigger"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hurray the configuration is done !&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This setup is working nicely here, my son is happy because he start himself the instance when he need. I am happy because it reduce &lt;strong&gt;a lot&lt;/strong&gt; the cost of this service. On the last 3 months I see that the EC2 Compute cost for this server is less than 1 US$ 😅 (around 17 US$ before the optimization) so &lt;strong&gt;95% less expensive&lt;/strong&gt; !&lt;/p&gt;

&lt;p&gt;Currently the configuration is made manually in the console, I would love to spend some time to change that one day, using for example the &lt;a href="https://docs.aws.amazon.com/cdk/latest/guide/home.html" rel="noopener noreferrer"&gt;CDK toolkit&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;It's also probably possible to manage the storage of the Minecraft world on S3 instead of the Instance EBS disk (some $$ to save here, but not a lot).&lt;/p&gt;

&lt;p&gt;It was a very fun project to build using multiple AWS services! Do you see other usages of dynamically boot EC2 instances using Lambda functions? Let me know in the comments!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>minecraft</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to use React inside a Wordpress application ?</title>
      <dc:creator>Julien Bras</dc:creator>
      <pubDate>Fri, 05 Mar 2021 20:49:43 +0000</pubDate>
      <link>https://dev.to/julbrs/how-to-use-react-inside-a-wordpress-application-49i</link>
      <guid>https://dev.to/julbrs/how-to-use-react-inside-a-wordpress-application-49i</guid>
      <description>&lt;h1&gt;
  
  
  The context
&lt;/h1&gt;

&lt;p&gt;I was asked a few weeks ago to build a new page on a existing Wordpress site, in order to build a "shop area":&lt;/p&gt;

&lt;p&gt;&lt;a href="https://distances.plus/le-coin-des-partenaires/" rel="noopener noreferrer"&gt;Link to page&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnfxkt6s63by95vnlydj2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnfxkt6s63by95vnlydj2.png" alt="Untitled"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I am not very efficient to work on Wordpress template system (not my cup of tea!), and I have now a solid background around React frontend. I want to see how it can be possible to integrate, on an existing Wordpress installation, one React application to produce this particular need.&lt;/p&gt;

&lt;p&gt;This article will explore the Wordpress/React options, then I will show you, &lt;strong&gt;step by step&lt;/strong&gt; how I have implemented a React application inside Wordpress. Finally I list you a few issues of the actual solution.&lt;/p&gt;

&lt;h1&gt;
  
  
  React with Wordpress?
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://reactjs.org/" rel="noopener noreferrer"&gt;React&lt;/a&gt;&lt;/strong&gt; is a popular javascript library that is generally used to build frontend application inside the browser. There is also a huge ecosystem of solutions around React (&lt;strong&gt;CreateReactApp&lt;/strong&gt;, &lt;strong&gt;NextJs&lt;/strong&gt;, &lt;strong&gt;Gatsby&lt;/strong&gt;...) that help to use the library in a reliable frontend application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://wordpress.org/" rel="noopener noreferrer"&gt;Wordpress&lt;/a&gt; is a very famous CMS (Content Management System) that is still used by a lot of website. It's very easy to use for content editor, and it comes with lots of plugins.&lt;/p&gt;

&lt;p&gt;There is multiple ways to mix Wordpress and React, but I will show you two examples here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a javascript frontend using Wordpress REST API
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb2ig91q1rvjbuzwwo81w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb2ig91q1rvjbuzwwo81w.png" alt="Untitled"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Wordpress comes with a nice &lt;a href="https://developer.wordpress.org/rest-api/" rel="noopener noreferrer"&gt;REST API&lt;/a&gt;, and so it's possible to build a classic Single Page Application (using CreateReactApp for example) that consume this API. Wordpress is still used to write articles, but the website generated is driven by a different frontend application. It's the &lt;em&gt;Headless CMS&lt;/em&gt; concept. This article is a great guide to achieve this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.freecodecamp.org/news/wordpress-react-how-to-create-a-modern-web-app-using-wordpress-ef6cc6be0cd0/" rel="noopener noreferrer"&gt;How to Create a Modern Web App Using WordPress and React&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.gatsbyjs.com" rel="noopener noreferrer"&gt;Gatsby&lt;/a&gt;, a static site builder using React, have also a dedicated solution here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.gatsbyjs.com/docs/how-to/sourcing-data/sourcing-from-wordpress/" rel="noopener noreferrer"&gt;Sourcing from WordPress&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This solution is a radical one for an already existing website, as you need to work on all existing content and transfert it to your new frontend solution. It's nice but it's too big for my own project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrate a React application inside Wordpress
&lt;/h2&gt;

&lt;p&gt;React is &lt;em&gt;only&lt;/em&gt; a simple javascript library. It's not needed to build an entire site, you can just load the library on a part of your existing page. From the &lt;a href="https://reactjs.org/docs/add-react-to-a-website.html" rel="noopener noreferrer"&gt;documentation of ReactJs&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;React has been designed from the start for gradual adoption, and you can use as little or as much React as you need. Perhaps you only want to add some “sprinkles of interactivity” to an existing page. React components are a great way to do that.&lt;/p&gt;

&lt;p&gt;The majority of websites aren’t, and don’t need to be, single-page apps. With a few lines of code and no build tooling, try React in a small part of your website. You can then either gradually expand its presence, or keep it contained to a few dynamic widgets.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I have a few article disussing how to add a React application in a Wordpress site. This one show that, but for the administration panel:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ghostinspector.com/blog/develop-wordpress-plugin-with-webpack-and-react" rel="noopener noreferrer"&gt;Ghost Inspector - Automated Website Testing and Monitoring&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I choose to go on this way because it's easier than rebuild the entire site, and it give me enough power to work like I want.&lt;/p&gt;

&lt;h1&gt;
  
  
  Integrate a React application in Wordpress
&lt;/h1&gt;

&lt;p&gt;I want to build a page, visible by end-users, that is loading a React application showing some articles of a particular category from the Wordpress website in a grid layout. This section will guide you in the creation of this page.&lt;/p&gt;

&lt;h2&gt;
  
  
  The big picture
&lt;/h2&gt;

&lt;p&gt;I will create a new wordpress &lt;strong&gt;plugin&lt;/strong&gt;. The plugin will show the React application if I use a specific &lt;strong&gt;short-code&lt;/strong&gt; in a page or an article. The React application will consume the &lt;strong&gt;REST API&lt;/strong&gt; of Wordpress to show the articles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a dedicated plugin
&lt;/h2&gt;

&lt;p&gt;To isolate the development I choose to work in a dedicated plugin. It is also possible to work in the theme &lt;code&gt;functions.php&lt;/code&gt; but I think it's cleaner to have a specific folder for this project.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;plugins&lt;/code&gt; folder of your wordpress application, make a new folder named &lt;code&gt;my-react-app&lt;/code&gt;. Create inside the folder a php file &lt;code&gt;my-react-app.php&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Inside &lt;code&gt;my-react-app&lt;/code&gt; let's bootstrap a new Create React App project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-react-app frontend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will create inside the folder &lt;code&gt;frontend&lt;/code&gt; a new React application using the class &lt;a href="https://create-react-app.dev" rel="noopener noreferrer"&gt;Create React App&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the php file you can put:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="cd"&gt;/**
 * Plugin Name: my-react-app
 * Plugin URI: a url
 * Description: A react application
 * Version: 0.1
 * Text Domain: my-react-app
 * Author: Julien Bras
 * Author URI: https://sidoine.org
 */&lt;/span&gt;

&lt;span class="c1"&gt;// First register resources with init &lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;my_react_app_init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/frontend/static"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'WP_ENV'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="s2"&gt;"development"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/frontend/build/static"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;wp_register_script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"my_react_app_js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;plugins_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"/js/main.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s2"&gt;"1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;wp_register_style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"my_react_app_css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;plugins_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"/css/main.css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s2"&gt;"1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"all"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'init'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'my_react_app_init'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Function for the short code that call React app&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;my_react_app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;wp_enqueue_script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"my_react_app_js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'1.0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;wp_enqueue_style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"my_react_app_css"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;div id=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;my_react_app&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;&amp;lt;/div&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;add_shortcode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'my_react_app'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'my_react_app'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will end with this structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;plugins
└── my-react-app
    ├── frontend
        │     ├── README.md
        │     ├── node_modules
        │     ├── package.json
        │     ├── .gitignore
        │     ├── public
        │     └── src
    └── my-react-app.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good ! The basic setup is now working ! Let's test it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Develop your React app
&lt;/h2&gt;

&lt;p&gt;Go into the &lt;code&gt;frontend&lt;/code&gt; folder. Start the development server by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="n"&gt;yarn&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;yarn&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;yarn&lt;/code&gt; by &lt;code&gt;npm&lt;/code&gt; if needed ! It will start a browser and show you this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvwec3tmerrqcm2fn4ydw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvwec3tmerrqcm2fn4ydw.png" alt="Untitled"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;You can start by editing any of the file under &lt;code&gt;frontend/src&lt;/code&gt; and actually develop your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build your React app
&lt;/h2&gt;

&lt;p&gt;In order to use your application in Wordpress you need to &lt;strong&gt;build&lt;/strong&gt; it. I haven't found yet a solution to develop the application directly inside Wordpress. To build the output for Wordpress, I recommend to rely on &lt;a href="https://github.com/gsoft-inc/craco" rel="noopener noreferrer"&gt;craco&lt;/a&gt;, a tool that can help to generate a single js file with predictable name.&lt;/p&gt;

&lt;p&gt;First install &lt;code&gt;craco&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="n"&gt;yarn&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;craco&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;craco&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create a &lt;code&gt;craco.config.js&lt;/code&gt; in &lt;code&gt;frontend&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// craco.config.js&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;webpack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;static/js/[name].js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;optimization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;runtimeChunk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;splitChunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;overrideWebpackConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;webpackConfig&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;webpackConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;static/css/[name].css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;webpackConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then edit the &lt;code&gt;package.json&lt;/code&gt; file for the &lt;code&gt;build&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scripts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;build&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;craco build&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Comment the &lt;code&gt;reportWebVitals();&lt;/code&gt; in &lt;code&gt;frontend/src/index.js&lt;/code&gt;: (it prevent from having a single js file, dont forget to remove the import too !)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// If you want to start measuring performance in your app, pass a function&lt;/span&gt;
&lt;span class="c1"&gt;// to log results (for example: reportWebVitals(console.log))&lt;/span&gt;
&lt;span class="c1"&gt;// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals&lt;/span&gt;
&lt;span class="c1"&gt;// reportWebVitals();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Modify the div id used in &lt;code&gt;frontend/src/index.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;ReactDOM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StrictMode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StrictMode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my_react_app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Modify the div id used in &lt;code&gt;frontend/public/index.html&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;noscript&amp;gt;&lt;/span&gt;You need to enable JavaScript to run this app.&lt;span class="nt"&gt;&amp;lt;/noscript&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"my_react_app"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the &amp;lt;body&amp;gt; tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's important to modify the id because by default the &lt;code&gt;root&lt;/code&gt; is too generic for something we will include on a Wordpress page.&lt;/p&gt;

&lt;p&gt;Add also a &lt;code&gt;homepage&lt;/code&gt; value in the &lt;code&gt;package.json&lt;/code&gt; (this will help for images):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;"version": "0.1.0",
"private": true,
"homepage": "/app/plugins/my-react-app/frontend/build/",
"dependencies": ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then test the build !&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;yarn&lt;/span&gt; &lt;span class="nx"&gt;build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will generate a &lt;code&gt;build&lt;/code&gt; folder inside &lt;code&gt;frontend&lt;/code&gt; (with a single &lt;code&gt;script.js&lt;/code&gt; file):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;yarn run v1.22.4
$ craco build
Creating an optimized production build...
Compiled successfully.

File sizes after gzip:

  41.86 KB  build/static/js/main.js
  518 B     build/static/css/main.css

The project was built assuming it is hosted at /app/plugins/my-react-app/frontend/build/.
You can control this with the homepage field in your package.json.

The build folder is ready to be deployed.

Find out more about deployment here:

  https://cra.link/deployment

✨  Done in 6.46s.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Test on Wordpress
&lt;/h2&gt;

&lt;p&gt;Login on your Wordpress installation and activate the &lt;code&gt;my-react-app&lt;/code&gt; plugin. Then in any page or article, use the short-code &lt;code&gt;[my_react_app]&lt;/code&gt;like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9he55wqxa2b87zbmxcn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9he55wqxa2b87zbmxcn.png" alt="Untitled"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;If you publish the page you will see:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0b4gjkz3ilw0ap843q0g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0b4gjkz3ilw0ap843q0g.png" alt="Untitled"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;It's a win 🏆 !&lt;/p&gt;

&lt;h2&gt;
  
  
  Use REST API
&lt;/h2&gt;

&lt;p&gt;Inside the React application it's very easy to consume the REST API. I am actually using a &lt;code&gt;API&lt;/code&gt; constant that point to the correct endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;API&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REACT_APP_API&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/wp-json`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So I am able to define the environment variable &lt;code&gt;REACT_APP_API&lt;/code&gt; in the &lt;code&gt;.env&lt;/code&gt; file if I want to not use the wordpress on the same host (development mode).&lt;/p&gt;

&lt;p&gt;Then inside a React component, I can use a &lt;code&gt;useEffect&lt;/code&gt; to populate a &lt;code&gt;items&lt;/code&gt; state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REACT_APP_CATEGORY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;_fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id,title,meta,content,featured_media,fimg_url,tags&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;per_page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;API&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/wp/v2/posts?&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;setItems&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Extra mile with Bedrock and Trellis
&lt;/h1&gt;

&lt;p&gt;On this particular application I am relying on &lt;a href="https://roots.io/bedrock/" rel="noopener noreferrer"&gt;Bedrock&lt;/a&gt;, a very good solution to develop on a Wordpress application with managed plugin, and on &lt;a href="https://roots.io/trellis/" rel="noopener noreferrer"&gt;Trellis&lt;/a&gt;, an other very food solution to facilitate the server provisioning and solution deployment (thanks &lt;a href="https://roots.io/" rel="noopener noreferrer"&gt;Roots&lt;/a&gt; !, I hope to test &lt;strong&gt;Sage&lt;/strong&gt; some day !)&lt;/p&gt;

&lt;p&gt;I have done the following to help me on this project&lt;/p&gt;

&lt;h2&gt;
  
  
  Using &lt;code&gt;mu-plugins&lt;/code&gt; folder
&lt;/h2&gt;

&lt;p&gt;Instead of deploying the plugin in &lt;code&gt;plugins&lt;/code&gt; I am using the &lt;code&gt;mu-plugins&lt;/code&gt; folder so I am sure the plugin is always loaded. Does not need a plugin activation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enhanced deploy procedure
&lt;/h2&gt;

&lt;p&gt;I want to deploy only the builded version, and never the &lt;code&gt;src&lt;/code&gt; folder. So each time I am deploying a new version I want to build my application and send only the &lt;code&gt;build&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;Inside my &lt;code&gt;trellis/group_vars/SERVER/main.yml&lt;/code&gt; I have added:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;deploy_build_before&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{{ playbook_dir }}/deploy-hooks/build-before-my-react-app.yml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will add a script before build time.&lt;/p&gt;

&lt;p&gt;Let's now create the  &lt;code&gt;build-before-my-react-app.yml&lt;/code&gt; file in &lt;code&gt;trellis/deploy-hooks&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Install&lt;/span&gt; &lt;span class="nx"&gt;npm&lt;/span&gt; &lt;span class="nx"&gt;dependencies&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;yarn&lt;/span&gt;
  &lt;span class="nx"&gt;delegate_to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;localhost&lt;/span&gt;
  &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;chdir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;{{ project_local_path }}/web/app/mu-plugins/my-react-app/frontend&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Compile&lt;/span&gt; &lt;span class="nx"&gt;assets&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;production&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;yarn&lt;/span&gt; &lt;span class="nx"&gt;build&lt;/span&gt;
  &lt;span class="nx"&gt;delegate_to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;localhost&lt;/span&gt;
  &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;chdir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;{{ project_local_path }}/web/app/mu-plugins/my-react-app/frontend&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Copy&lt;/span&gt; &lt;span class="nx"&gt;production&lt;/span&gt; &lt;span class="nx"&gt;assets&lt;/span&gt;
  &lt;span class="nx"&gt;synchronize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;{{ project_local_path }}/web/app/mu-plugins/my-react-app/frontend/build/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="nx"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;{{ deploy_helper.new_release_path }}/web/app/mu-plugins/my-react-app/frontend&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="nx"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;no&lt;/span&gt;
    &lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;no&lt;/span&gt;
    &lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;yes&lt;/span&gt;
    &lt;span class="nx"&gt;rsync_opts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;chmod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;Du&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;rwx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;chmod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;Dg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;rx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;chmod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;Do&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;rx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;chmod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;Fu&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;rw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;chmod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;Fg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;chmod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;Fo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Thanks for the &lt;a href="https://github.com/roots/trellis/blob/master/deploy-hooks/build-before.yml" rel="noopener noreferrer"&gt;Sage 9 build-before example&lt;/a&gt;&lt;/em&gt; 😉&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion and some concerns
&lt;/h1&gt;

&lt;p&gt;As it's a React application I have some concerns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SEO&lt;/strong&gt;: Google will probably not understand well my page...&lt;/li&gt;
&lt;li&gt;managing correctly CSS is tricky because the Wordpress page will inject some css classes (that you will not see in development mode using &lt;code&gt;yarn start&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This project have been realized because the classic plugin we were using for this kind of page (&lt;a href="https://wpbakery.com" rel="noopener noreferrer"&gt;WPBakery&lt;/a&gt;) doesn't come out-of-the-box with filtering and ordering capabilities. Some &lt;a href="https://codecanyon.net/item/visual-composer-sortable-grid-taxonomy-filter/7338639" rel="noopener noreferrer"&gt;options&lt;/a&gt; are available but limited in personalization. And it's fun to put some new tooling in a classic existing web application ! Get a try !&lt;/p&gt;

</description>
      <category>react</category>
      <category>wordpress</category>
      <category>bedrock</category>
      <category>trellis</category>
    </item>
    <item>
      <title>Jira: one easy solution to generate custom reporting</title>
      <dc:creator>Julien Bras</dc:creator>
      <pubDate>Fri, 19 Feb 2021 03:53:43 +0000</pubDate>
      <link>https://dev.to/julbrs/jira-one-easy-solution-to-generate-custom-reporting-102j</link>
      <guid>https://dev.to/julbrs/jira-one-easy-solution-to-generate-custom-reporting-102j</guid>
      <description>&lt;h1&gt;
  
  
  Jira is a powerful tool
&lt;/h1&gt;

&lt;p&gt;As a Product Owner, I am using a lot &lt;a href="https://www.atlassian.com/software/jira"&gt;Jira&lt;/a&gt; in the company I am working for. It's my main tool and it's a very common solution to manage software development. It comes with a lot of capabilites for managing Agile team, and lots of reports:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XM8TgcF4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l4scgw2e03rmypl8uqp2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XM8TgcF4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l4scgw2e03rmypl8uqp2.png" alt="Jira Reports" width="800" height="687"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Feel the limits
&lt;/h1&gt;

&lt;p&gt;But sometimes I feel a bit limited by the output generated by the tool. The more we are using it and feeding data in it, the more information I want to be able to read.&lt;/p&gt;

&lt;p&gt;A typical exemple: for each task each user is providing the time spent for different activities: development, review, test... We are adding &lt;em&gt;tags&lt;/em&gt; in the comment box of the work log:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NCf3eoIy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ksyklex7pp08y58sclcq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NCf3eoIy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ksyklex7pp08y58sclcq.png" alt="Work log" width="401" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So it's really easy to put the data in Jira but there is no pre-built system to produce an analysis for each sprint and know how many time have been spent for &lt;code&gt;#DEV&lt;/code&gt; or &lt;code&gt;#TEST&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Or we are using &lt;em&gt;Epics&lt;/em&gt; to track our most important features, but the time spent is at the Story level, there is no easy way to know how many time a specific Epic took.&lt;/p&gt;

&lt;p&gt;Sometime a plugin can answer but I want to find a more generic solution to this issue. How can I produce the report I want to produce ?&lt;/p&gt;

&lt;h1&gt;
  
  
  Introducing the API and Jupyter
&lt;/h1&gt;

&lt;p&gt;Jira comes with a very powerful &lt;a href="https://developer.atlassian.com/server/jira/platform/rest-apis/"&gt;REST API&lt;/a&gt;. So the data can be accessed by this way very easily. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://jupyter.org/"&gt;Jupyter Notebook&lt;/a&gt; is &lt;em&gt;an open-source web application that allows you to create and share documents that contain live code, equations, visualizations and narrative text&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LAtFQgLu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rgqs516hgzccifd4znjv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LAtFQgLu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rgqs516hgzccifd4znjv.png" alt="Jupyter Notebooks" width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So &lt;strong&gt;yes&lt;/strong&gt;, it's possible to write a bit of python code that will connect to Jira Cloud (or Jira Server if you run Jupyter not so far away the Jira Server instance)&lt;/p&gt;

&lt;p&gt;This small section will list all &lt;em&gt;Epics&lt;/em&gt; for a specific project and a specific version, and generate a nice table (using &lt;a href="https://pandas.pydata.org/"&gt;pandas&lt;/a&gt;), with a footer for a sum of estimation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Show epics with consumed time and estimated time&lt;/span&gt;
project &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"MYPROJECT"&lt;/span&gt;
version &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;
issues &lt;span class="o"&gt;=&lt;/span&gt; jira.search_issues&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'project = "{project}" and issuetype = "Epic" and fixversion = {version} ORDER BY key DESC'&lt;/span&gt;.format&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;project, &lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;version&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="nv"&gt;maxResults&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0,startAt&lt;span class="o"&gt;=&lt;/span&gt;0&lt;span class="o"&gt;)&lt;/span&gt;
formated_issues &lt;span class="o"&gt;=&lt;/span&gt; list&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;issue &lt;span class="k"&gt;in &lt;/span&gt;issues:
  &lt;span class="c"&gt;# compute estimated time&lt;/span&gt;
  estimated &lt;span class="o"&gt;=&lt;/span&gt; 0
  &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;issue.fields.customfield_10663&lt;span class="o"&gt;)&lt;/span&gt;:
    estimated &lt;span class="o"&gt;=&lt;/span&gt; issue.fields.customfield_10663

  &lt;span class="c"&gt;# compute spentTime from linked issues&lt;/span&gt;
  spent_time &lt;span class="o"&gt;=&lt;/span&gt; 0
  child_issues &lt;span class="o"&gt;=&lt;/span&gt; jira.search_issues&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'parent = {epic}'&lt;/span&gt;.format&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;epic&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;issue.key&lt;span class="o"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;child_issue &lt;span class="k"&gt;in &lt;/span&gt;child_issues:
    try:
      spent_time +&lt;span class="o"&gt;=&lt;/span&gt; child_issue.fields.timespent
    except TypeError:
      &lt;span class="c"&gt;# No spent time on this one !&lt;/span&gt;
      spent_time +&lt;span class="o"&gt;=&lt;/span&gt; 0

  formated_issues.append&lt;span class="o"&gt;([&lt;/span&gt;issue.key, issue.fields.status.name, issue.fields.labels, issue.fields.summary, estimated, spent_time]&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; pd.DataFrame&lt;span class="o"&gt;(&lt;/span&gt;formated_issues, &lt;span class="nv"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;, &lt;span class="s2"&gt;"status"&lt;/span&gt;, &lt;span class="s2"&gt;"labels"&lt;/span&gt;, &lt;span class="s2"&gt;"summary"&lt;/span&gt;, &lt;span class="s2"&gt;"estimatedTime"&lt;/span&gt;, &lt;span class="s2"&gt;"spentTime"&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt;
&lt;span class="nb"&gt;df&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"spentTime"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; round&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;df&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"spentTime"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; /3600 /7, 1&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;df&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'ratio'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; round&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;df&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'spentTime'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; / &lt;span class="nb"&gt;df&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'estimatedTime'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;100, 0&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; df.fillna&lt;span class="o"&gt;(&lt;/span&gt;0&lt;span class="o"&gt;)&lt;/span&gt;.sort_values&lt;span class="o"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;"status"&lt;/span&gt;, &lt;span class="s2"&gt;"ratio"&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt;
&lt;span class="nb"&gt;df&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;df&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;.apply&lt;span class="o"&gt;(&lt;/span&gt;lambda x: &lt;span class="s1"&gt;'&amp;lt;a href="https://mydomain.atlassian.net/browse/{0}"&amp;gt;{0}&amp;lt;/a&amp;gt;'&lt;/span&gt;.format&lt;span class="o"&gt;(&lt;/span&gt;x&lt;span class="o"&gt;))&lt;/span&gt;
&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; df.append&lt;span class="o"&gt;(&lt;/span&gt;pd.DataFrame&lt;span class="o"&gt;([&lt;/span&gt;
                             &lt;span class="o"&gt;[&lt;/span&gt;df.id.count&lt;span class="o"&gt;()&lt;/span&gt;, df.estimatedTime.sum&lt;span class="o"&gt;()&lt;/span&gt;, df.spentTime.sum&lt;span class="o"&gt;()&lt;/span&gt;, round&lt;span class="o"&gt;(&lt;/span&gt;df.spentTime.sum&lt;span class="o"&gt;()&lt;/span&gt;/df.estimatedTime.sum&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;100&lt;span class="o"&gt;)]]&lt;/span&gt;, 
                            index &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Total"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;, 
                            &lt;span class="nv"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;, &lt;span class="s2"&gt;"estimatedTime"&lt;/span&gt;, &lt;span class="s2"&gt;"spentTime"&lt;/span&gt;, &lt;span class="s2"&gt;"ratio"&lt;/span&gt;&lt;span class="o"&gt;]))&lt;/span&gt;
HTML&lt;span class="o"&gt;(&lt;/span&gt;df.to_html&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;escape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;False&lt;span class="o"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here is the generated report (and yes there is a link to go directly on each case):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--k7i956eb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xiaaw1iunzzbd81dyz3k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k7i956eb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xiaaw1iunzzbd81dyz3k.png" alt="Jira Data" width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have started by using this method with Jupyter, based on this article: &lt;a href="https://blog.isostech.com/atlassian/using-jupyter-notebooks-to-access-jira"&gt;Using Jupyter Notebooks to Access Jira&lt;/a&gt;. But (there is always a but! 😅) I was thinking that it's too time-consuming to start the Jupyter via docker, and it's not very easy to share a document to a co-worker:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;OK send me the Jupyter file by email and give me the procedure to launch the Jupyter image on my computer. Oh wait, I need to start Docker too ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is probably a more &lt;em&gt;easy&lt;/em&gt; way to do that no ?&lt;/p&gt;
&lt;h1&gt;
  
  
  Introducing Colaboratory
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://colab.research.google.com/"&gt;Colaboratory&lt;/a&gt;, or Colab when you are in a rush, is a service operated by Google that is running &lt;strong&gt;Jupyter notebook&lt;/strong&gt; for you (yeah no more server to handle !). And it's very easy to share notebooks via &lt;strong&gt;Google Drive&lt;/strong&gt; or &lt;strong&gt;Github&lt;/strong&gt; for example.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kRfJwYZj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5hlejk644e7knr8kybch.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kRfJwYZj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5hlejk644e7knr8kybch.png" alt="Colaboratory" width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's not the only one (&lt;a href="https://notebooks.azure.com/"&gt;Azure Notebooks&lt;/a&gt; too) but our company is relying a lot on Google Drive, so it's easier to share documents.&lt;/p&gt;

&lt;p&gt;As of now I have written 6 or 7 notebooks that helps me dealing with Jira data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how to plan a 3-months roadmap for a new version,&lt;/li&gt;
&lt;li&gt;how to review my previous version and check why this Epic have not been delivered in time&lt;/li&gt;
&lt;li&gt;how to build sprint dashboard that give me a good vision of what have been done&lt;/li&gt;
&lt;li&gt;and so much...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally I can share with you some examples. I have created a dedicated repository on github, so it is easy to generate a link to a specific notebook.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/julbrs"&gt;
        julbrs
      &lt;/a&gt; / &lt;a href="https://github.com/julbrs/jira-notebooks"&gt;
        jira-notebooks
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Jupyter Notebooks for Jira
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1 id="user-content-jira-notebooks"&gt;&lt;a class="heading-link" href="https://github.com/julbrs/jira-notebooks#jira-notebooks"&gt;Jira Notebooks&lt;/a&gt;&lt;/h1&gt;
&lt;h2 id="user-content-what-"&gt;&lt;a class="heading-link" href="https://github.com/julbrs/jira-notebooks#what-"&gt;What ?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This repository is hosting a few Jupyter Notebooks that consume Jira data.&lt;/p&gt;
&lt;p&gt;Here is the article linked to this repository: &lt;a href="https://dev.to/bobman38/jira-one-easy-solution-to-generate-custom-reporting-102j" rel="nofollow"&gt;Jira: one easy solution to generate custom reporting&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/julbrs/jira-notebooksexample.gif"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BDkJtVz_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://github.com/julbrs/jira-notebooksexample.gif" alt="In action !"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="user-content-who-"&gt;&lt;a class="heading-link" href="https://github.com/julbrs/jira-notebooks#who-"&gt;Who ?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I (Julien Bras) am the owner of the notebooks here.&lt;/p&gt;
&lt;h2 id="user-content-contribute-"&gt;&lt;a class="heading-link" href="https://github.com/julbrs/jira-notebooks#contribute-"&gt;Contribute ?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Feel free to make a PR on this repository to share some Jupyter Notebooks around Jira. Be sure to provide a document without any data.&lt;/p&gt;
&lt;/div&gt;

  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/julbrs/jira-notebooks"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;One notebook on colab : &lt;a href="https://colab.research.google.com/github/bobman38/jira-notebooks/blob/main/jira_roadmap_plan.ipynb"&gt;jira_roadmap_plan.ipynb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--F0d7r9j5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gy0ikeocltpsv604067a.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--F0d7r9j5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gy0ikeocltpsv604067a.gif" alt="In action !" width="800" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Do not hesitate to create a PR on the repository if you want to share some nice looking notebook for Jira! Thanks for reading !&lt;/p&gt;

</description>
      <category>jira</category>
      <category>jupyter</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Beginner guide to Maven Central publishing</title>
      <dc:creator>Julien Bras</dc:creator>
      <pubDate>Tue, 05 May 2020 03:35:06 +0000</pubDate>
      <link>https://dev.to/julbrs/beginner-guide-to-maven-central-publishing-3jio</link>
      <guid>https://dev.to/julbrs/beginner-guide-to-maven-central-publishing-3jio</guid>
      <description>&lt;p&gt;Maven Central is the central repository for Maven, the main Java repository where you can find libraries and components. Java developers are using this repository to create java application and manage efficiently dependencies.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I use to be very intimidated by pushing an artifact (i.e. a java package) to Maven Central, as it seems very complicated to me by the past. For example push to &lt;code&gt;npmjs&lt;/code&gt; seems more easy (&lt;code&gt;npm publish&lt;/code&gt; !).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This article is just a guide (at least for myself) on how to push easily some content to Maven Central, with just a &lt;em&gt;Github&lt;/em&gt; account.&lt;/p&gt;

&lt;h1&gt;
  
  
  Before starting why pushing to Central ?
&lt;/h1&gt;

&lt;p&gt;Maybe you are asking yourself why pushing to Maven Central when we have in 2020 multiple way of releasing java package ?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maven Central is the main repository. You don't have to declare an additional &lt;code&gt;repository&lt;/code&gt; in your &lt;code&gt;pom.xml&lt;/code&gt;, it is just available by default in your Maven/Gradle... project.&lt;/li&gt;
&lt;li&gt;Other main repositories are generally synced to this main one. Here I will show you how to publish to &lt;a href="https://central.sonatype.org/pages/ossrh-guide.html"&gt;OSSRH&lt;/a&gt; (OSS Repository Hosting) that is the main way to publish package. This repository is synced to Maven Central.&lt;/li&gt;
&lt;li&gt;There is not so much other solution available as pure repositories, for free (I want to publish open source content, I don't want to pay for that)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/features/packages"&gt;GitHub Package&lt;/a&gt; seems very promising &lt;strong&gt;but&lt;/strong&gt; end-users must manipulate the &lt;code&gt;repositories&lt;/code&gt; part of the &lt;code&gt;pom.xml&lt;/code&gt; file and must be &lt;a href="https://github.community/t5/GitHub-API-Development-and/Download-from-Github-Package-Registry-without-authentication/td-p/35255"&gt;authenticated&lt;/a&gt; even for public packages. &lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  First step, create account and get a groupId
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;create a JIRA account &lt;a href="https://issues.sonatype.org/secure/Signup!default.jspa"&gt;here&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Create a new issue using &lt;a href="https://issues.sonatype.org/secure/CreateIssue.jspa?issuetype=21&amp;amp;pid=10134"&gt;this&lt;/a&gt; template.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gpenQWIu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/cw6o2r6e3okytavs8hol.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gpenQWIu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/cw6o2r6e3okytavs8hol.png" alt="Alt Text" width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The issue must describe the project you want to share. The most important information in the case is the &lt;code&gt;groupId&lt;/code&gt;. The &lt;code&gt;groupId&lt;/code&gt; is the main identification of you as a package provider, so it must be unique. It is generally an url in reverse order like : &lt;code&gt;to.dev&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For a hobbyist developer using github or equivalent git platform I recommand the following : &lt;code&gt;io.github.YOURUSERNAME&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once the ticket is created, you will have to prove that you &lt;em&gt;own&lt;/em&gt; the &lt;code&gt;groupId&lt;/code&gt; provided (for GitHub it's just a matter of create a new temporary project repo) &lt;/p&gt;

&lt;h1&gt;
  
  
  Second step, make your project pretty
&lt;/h1&gt;

&lt;p&gt;Here is the small maven project I want to share on Central:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/wiiisdom"&gt;
        wiiisdom
      &lt;/a&gt; / &lt;a href="https://github.com/wiiisdom/biar-manager"&gt;
        biar-manager
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      BIAR BI ARchive File library
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;It's a very simple library that is designed to read a specific file format. &lt;/p&gt;

&lt;p&gt;There is a long list of requirement for your project to be accepted on OSSRH, you can read it &lt;a href="https://central.sonatype.org/pages/requirements.html"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To resume the requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you must supply javadoc (using &lt;code&gt;maven-javadoc-plugin&lt;/code&gt; for example)&lt;/li&gt;
&lt;li&gt;you must supply sources (using &lt;code&gt;maven-source-plugin&lt;/code&gt; for example)&lt;/li&gt;
&lt;li&gt;you must sign the files with GPG (using &lt;code&gt;maven-gpg-plugin&lt;/code&gt; for example)&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;pom.xml&lt;/code&gt; file must contains enough metadata:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;groupId&lt;/code&gt; (unique namespace like &lt;code&gt;io.github.USERNAME&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;artifactId&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;version&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;description&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;url&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;licenses&lt;/code&gt; part&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;developers&lt;/code&gt; part&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scm&lt;/code&gt; section&lt;/li&gt;
&lt;/ul&gt;


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

&lt;p&gt;For reference here his my &lt;a href="https://github.com/bobman38/biar-manager/blob/master/pom.xml"&gt;pom.xml&lt;/a&gt; file that fit the requirements.&lt;/p&gt;

&lt;h1&gt;
  
  
  Finally deploy !
&lt;/h1&gt;

&lt;p&gt;Do not forget to install gpg and create a new key if you don't have one (I found again my key generated in 2004 yes I am old !)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brew install gpg # mac example
gpg --gen-key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using Maven it is needed to use the right &lt;code&gt;distributionManagement&lt;/code&gt; and help yourself by using the &lt;code&gt;nexus-staging-maven-plugin&lt;/code&gt; plugin:&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;distributionManagement&amp;gt;
  &amp;lt;snapshotRepository&amp;gt;
    &amp;lt;id&amp;gt;ossrh&amp;lt;/id&amp;gt;
    &amp;lt;url&amp;gt;https://oss.sonatype.org/content/repositories/snapshots&amp;lt;/url&amp;gt;
  &amp;lt;/snapshotRepository&amp;gt;
&amp;lt;/distributionManagement&amp;gt;
&amp;lt;build&amp;gt;
  &amp;lt;plugins&amp;gt;
    &amp;lt;plugin&amp;gt;
      &amp;lt;groupId&amp;gt;org.sonatype.plugins&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;nexus-staging-maven-plugin&amp;lt;/artifactId&amp;gt;
      &amp;lt;version&amp;gt;1.6.7&amp;lt;/version&amp;gt;
      &amp;lt;extensions&amp;gt;true&amp;lt;/extensions&amp;gt;
      &amp;lt;configuration&amp;gt;
        &amp;lt;serverId&amp;gt;ossrh&amp;lt;/serverId&amp;gt;
        &amp;lt;nexusUrl&amp;gt;https://oss.sonatype.org/&amp;lt;/nexusUrl&amp;gt;
        &amp;lt;autoReleaseAfterClose&amp;gt;true&amp;lt;/autoReleaseAfterClose&amp;gt;
      &amp;lt;/configuration&amp;gt;
    &amp;lt;/plugin&amp;gt;
    ...
  &amp;lt;/plugins&amp;gt;
&amp;lt;/build&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(again this is available in my &lt;a href="https://github.com/bobman38/biar-manager/blob/master/pom.xml"&gt;pom.xml&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;You must auth on the OSSRH repository by modifying your &lt;code&gt;settings.xml&lt;/code&gt; file under &lt;code&gt;~/.m2&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;&amp;lt;settings&amp;gt;
  &amp;lt;servers&amp;gt;
    &amp;lt;server&amp;gt;
      &amp;lt;id&amp;gt;ossrh&amp;lt;/id&amp;gt;
      &amp;lt;username&amp;gt;your-jira-id&amp;lt;/username&amp;gt;
      &amp;lt;password&amp;gt;your-jira-pwd&amp;lt;/password&amp;gt;
    &amp;lt;/server&amp;gt;
  &amp;lt;/servers&amp;gt;
&amp;lt;/settings&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the id and password you have created at the start of this article.&lt;/p&gt;

&lt;p&gt;And then if your maven project have already the right version you can directly push to OSSRH using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mvn clean deploy 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The property &lt;code&gt;autoReleaseAfterClose&lt;/code&gt; of &lt;code&gt;nexus-staging-maven-plugin&lt;/code&gt;  set to true will directly push the artifact from snapshot to the release repository.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; to change your Maven project version you can use the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mvn versions:set -DnewVersion=1.2.3 # set the version
mvn versions:commit # remove the pomBackup file
mvn versions:revert # back to previous version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a few hours you will be able to see your package on &lt;a href="https://search.maven.org"&gt;Maven Search&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IakXQUiv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/ef3nh41vf7vyqh8lm7wt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IakXQUiv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/ef3nh41vf7vyqh8lm7wt.png" alt="Alt Text" width="800" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nice job !&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Other stuff to speak of
&lt;/h1&gt;

&lt;p&gt;During my exploration I have found this tool : &lt;a href="https://jitpack.io/"&gt;https://jitpack.io/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8wmLriO1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/uasrmypspx2g5l5ggw4g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8wmLriO1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/uasrmypspx2g5l5ggw4g.png" alt="Alt Text" width="800" height="596"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It seems to be an easy way to consume an existing git repository as maven dependency. I have not yet tested it but it feels like the &lt;em&gt;flexible&lt;/em&gt; I miss with Maven (&lt;em&gt;Composer&lt;/em&gt; for PHP allow to consume git repo, &lt;em&gt;npmjs&lt;/em&gt; allow to consume git repo, etc...)&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;This article rely a lot on the OSSRH guide &lt;a href="https://central.sonatype.org/pages/ossrh-guide.html"&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Well there is more steps than &lt;code&gt;npmjs&lt;/code&gt; but it is very affordable and you can do the same. Maybe it can be also the time for me to explore &lt;a href="https://gradle.org/"&gt;Gradle&lt;/a&gt; or other new tooling around Java... Let me know if you have already pushed an artifact to Maven Central ? Thanks for reading !&lt;/p&gt;

</description>
      <category>maven</category>
      <category>java</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Moving from Eclipse to VSCode by a Java Developer</title>
      <dc:creator>Julien Bras</dc:creator>
      <pubDate>Sun, 03 May 2020 01:11:11 +0000</pubDate>
      <link>https://dev.to/julbrs/moving-from-eclipse-to-vscode-by-a-java-developer-4ji7</link>
      <guid>https://dev.to/julbrs/moving-from-eclipse-to-vscode-by-a-java-developer-4ji7</guid>
      <description>&lt;p&gt;I am working for a software editor and we mainly use &lt;em&gt;Java&lt;/em&gt; as backend language. I use to work with &lt;strong&gt;Eclipse&lt;/strong&gt; since around 2010, only for Java projects. Here is my journey and a quick comparison of the tools.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;First of all I need to inform that I am actually &lt;em&gt;Product Owner&lt;/em&gt; and not anymore a full-time developer. So I am still looking at Java source code project, but with a different level of usage than before. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  VSCode setup
&lt;/h1&gt;

&lt;p&gt;VSCode is relying a lot on extension. Each extension add a little extra power to the tool. So the initial text editor can be compared with a fully featured IDE once the right extensions have been installed.&lt;/p&gt;

&lt;p&gt;I don't want to present each extension, I found &lt;a href="https://blog.usejournal.com/visual-studio-code-for-java-the-ultimate-guide-2019-8de7d2b59902" rel="noopener noreferrer"&gt;this article&lt;/a&gt; which is describing that very well.&lt;/p&gt;

&lt;p&gt;Globally you can relly on the &lt;a href="https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-pack" rel="noopener noreferrer"&gt;Java Extension Pack&lt;/a&gt; that install all the main Java extensions for you.&lt;/p&gt;

&lt;h1&gt;
  
  
  Import a project?
&lt;/h1&gt;

&lt;p&gt;This is I think the most important change between Eclipse and VSCode. &lt;/p&gt;

&lt;p&gt;Eclipse is relying on a &lt;em&gt;workspace&lt;/em&gt; concept where you import Java projects. My main concern with this approch is around multi-modules Maven projects : once you add or remove a module, Eclipse is &lt;em&gt;lost&lt;/em&gt; and you need to import again the missing module.&lt;/p&gt;

&lt;p&gt;VSCode is more like other editor (Atom...), and you can simply open a folder that contain your multi-modules Maven project. If some project have been removed then you will not see it anymore.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fdaw2edkujg1ur997qbz3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fdaw2edkujg1ur997qbz3.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Folder presentation in VSCode&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fsn9300ruox89z4zpuakp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fsn9300ruox89z4zpuakp.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Workspace in Eclipse&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It's not a big difference but for me it is more easy to switch between projects. I do not loose anymore time to import projects, I just open the right folder. There is also a &lt;em&gt;workspace&lt;/em&gt; system in VSCode to open multiple folder at once, it may be useful if you work on multiple projects at the same time (front and back for example).&lt;/p&gt;

&lt;h1&gt;
  
  
  Develop ?
&lt;/h1&gt;

&lt;p&gt;The global experience is very good. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frnwav1g9tevcdaznurws.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frnwav1g9tevcdaznurws.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You still have auto-completion and JavaDoc is shown when it's necessary. There is also an equivalent of the &lt;em&gt;Run Configuration&lt;/em&gt; with the &lt;em&gt;Run&lt;/em&gt; panel to fire your project. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fidx3t1pjoj705tyw8kai.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fidx3t1pjoj705tyw8kai.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;Run&lt;/em&gt; panel rely on a &lt;code&gt;launch.json&lt;/code&gt; file, and it can be saved on your git repository if you want to share it with team-workers. &lt;/p&gt;

&lt;h1&gt;
  
  
  Test ?
&lt;/h1&gt;

&lt;p&gt;The right extension help to run tests. There is also some &lt;em&gt;helpers&lt;/em&gt; to run the test directly before the test method or the test class.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fk979g2mqno0sf1s3s6fd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fk979g2mqno0sf1s3s6fd.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Share your code ?
&lt;/h1&gt;

&lt;p&gt;Git is directly available in VSCode. I have never rely on any Git addon in Eclipse, as I found some products buggy. So I was relying only on the Git command line. I still rely a lot on the command line but I am happy to see this very good integration of Git directly into the product.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fdfd6upqgjcyxia2i8od6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fdfd6upqgjcyxia2i8od6.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And you have some indicator directly in the editor (green if new line, red if removed lines...). I have never see this kind of indication inside Eclipse. Probably I have never installed the right extension ;)&lt;/p&gt;

&lt;h1&gt;
  
  
  Run tooling (Maven, etc) ?
&lt;/h1&gt;

&lt;p&gt;There is also a Maven extension that let you execute all Maven commands. But here I prefer using the excellent terminal that let you do what you want. It's here, just use it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyg4vpndownzvzyog0m4q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyg4vpndownzvzyog0m4q.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;One month after VSCode installation and first test on Java projects, I realize that I haven't opened anymore Eclipse. Just today to make a screenshot...&lt;br&gt;
I have not yet speak of the performance too. I have a decent MBP and Eclipse take always a couple of seconds to startup. VSCode start in less than a second.&lt;br&gt;
Last, I am not only coding in Java but also Javascript. I have originally installed &lt;em&gt;VSCode&lt;/em&gt; to replace &lt;em&gt;Atom&lt;/em&gt; editor for my JS projects... So I am happy to be able to use a single tool for all my programming needs. It's faster and I am more comfortable to use a single tool for coding.&lt;br&gt;
&lt;strong&gt;So just an advice: get a try on one of your Java project, you may be surprised to change your habits !&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>eclipse</category>
      <category>vscode</category>
      <category>java</category>
    </item>
    <item>
      <title>Add an Admin Panel to an existing Express API / React frontend</title>
      <dc:creator>Julien Bras</dc:creator>
      <pubDate>Mon, 13 Apr 2020 14:15:17 +0000</pubDate>
      <link>https://dev.to/julbrs/add-an-admin-panel-to-an-existing-express-api-react-frontend-30lj</link>
      <guid>https://dev.to/julbrs/add-an-admin-panel-to-an-existing-express-api-react-frontend-30lj</guid>
      <description>&lt;h1&gt;
  
  
  Context
&lt;/h1&gt;

&lt;p&gt;Here is a quick personal review of JS solution for generating an Admin Panel today (April 2020). The goal is to add an admin panel to an existing Express API backend + React frontend.&lt;/p&gt;

&lt;p&gt;I want to have something comparable to the &lt;a href="https://docs.djangoproject.com/en/3.0/ref/contrib/admin/" rel="noopener noreferrer"&gt;Django Admin&lt;/a&gt; as a reference.&lt;/p&gt;

&lt;p&gt;It must manage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;some kind of authentication&lt;/li&gt;
&lt;li&gt;some kind of file management, ideally with &lt;em&gt;S3 hosting&lt;/em&gt; as the backend is running on &lt;em&gt;Heroku&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;I am OK to add modules to my existing Express application, or change completely the backend.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  AdminBro
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FSoftwareBrothers%2Fadmin-bro%2Fmaster%2Fdocs%2Fanim.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FSoftwareBrothers%2Fadmin-bro%2Fmaster%2Fdocs%2Fanim.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just an admin-panel to add on Express app. There is no easy way to add a file upload feature (&lt;a href="https://adminbro.com/DropZone.html" rel="noopener noreferrer"&gt;this&lt;/a&gt; can help). But it is a great tool to not modify to much my existing solution.&lt;/p&gt;

&lt;h1&gt;
  
  
  Strapi.io
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://camo.githubusercontent.com/7f368b156e23f4ca24f1864829a4f5e2d743ce5f/68747470733a2f2f7374726170692e696f2f6173736574732f696d616765732f726561646d652e706e67" class="article-body-image-wrapper"&gt;&lt;img src="https://camo.githubusercontent.com/7f368b156e23f4ca24f1864829a4f5e2d743ce5f/68747470733a2f2f7374726170692e696f2f6173736574732f696d616765732f726561646d652e706e67"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This tool is more a &lt;em&gt;Content Management System&lt;/em&gt; than a &lt;em&gt;Framework&lt;/em&gt;. It define itself as a &lt;code&gt;headless CMS&lt;/code&gt;. I can find some &lt;em&gt;Drupal&lt;/em&gt; inspiration inside the tool:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tool to manage different Content-Type (entities)&lt;/li&gt;
&lt;li&gt;plugin system to add authentication (lots of providers supported)&lt;/li&gt;
&lt;li&gt;support a classic database or MongoDB&lt;/li&gt;
&lt;li&gt;obviously there is an admin panel to manage the entities&lt;/li&gt;
&lt;li&gt;you can manage files, and it can be handled by default on various providers including S3 (good !) &lt;/li&gt;
&lt;li&gt;it provide out of the box a REST api, and you can also activate a GraphQL api easily.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is a very fast way to create a headless backend. But as it rely a lot on plugins, you may not find exactly the feature you need. For example there no &lt;em&gt;Internationalization&lt;/em&gt; &lt;a href="https://medium.com/strapi/content-internationalization-with-strapi-507ef5869c15" rel="noopener noreferrer"&gt;plugin&lt;/a&gt; &lt;em&gt;yet&lt;/em&gt; to manage multiple language.&lt;/p&gt;

&lt;h1&gt;
  
  
  Feathersjs
&lt;/h1&gt;

&lt;p&gt;This is a framework. No admin interface.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;entities are service (very easy to add a new service !)&lt;/li&gt;
&lt;li&gt;handle different backend including Mongo&lt;/li&gt;
&lt;li&gt;no admin webapp backend but there is a &lt;a href="https://github.com/josx/ra-data-feathers" rel="noopener noreferrer"&gt;react-admin plugin&lt;/a&gt; ! &lt;a href="https://marmelab.com/react-admin/" rel="noopener noreferrer"&gt;react-admin&lt;/a&gt; is a solution to add an admin panel directly in a react application&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;react-admin&lt;/em&gt; is not easy to implement !&lt;/li&gt;
&lt;li&gt;nothing for file upload out of the box&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Back4app
&lt;/h1&gt;

&lt;p&gt;This is a hosted version of Parse server. Parse use to be a tool provided by Facebook, but it now an open-source tool &lt;a href="https://parseplatform.org/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. It seems we cannot consume data as raw RESTapi but rather with a dedicated SDK.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fpehv19phaxy3hv5q5fre.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fpehv19phaxy3hv5q5fre.png" alt="Back4app"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Provide lots of stuff out of the box&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API to consume the data&lt;/li&gt;
&lt;li&gt;Authentication with multiple providers&lt;/li&gt;
&lt;li&gt;Admin Panel (yes it is mandatory for my little selection !)&lt;/li&gt;
&lt;li&gt;File management&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  React-Admin
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://marmelab.com/react-admin" rel="noopener noreferrer"&gt;This&lt;/a&gt; is a React library that can generate an admin panel. It rely on existing REST or GraphQL API , with a a DataProvider object that explain how to communicate with the API. It is needed to write the DataProvider if your API does not stick exactly with an &lt;a href="https://marmelab.com/react-admin/DataProviders.html#available-providers" rel="noopener noreferrer"&gt;already available&lt;/a&gt; DataProvider. In my case the &lt;em&gt;Simple REST&lt;/em&gt; was not compatible directly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fw61u9whu585ds3dq8zw2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fw61u9whu585ds3dq8zw2.png" alt="react-admin"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;I was pushing hard on &lt;em&gt;AdminBro&lt;/em&gt; but I wasn't able to handle a clean and fast solution for one of my entities that is linked to a file. The file management seems not obvious for me ! &lt;em&gt;AdminBro&lt;/em&gt; is not hard to install and setup but it require customization to fit exactly with your existing Express application. My main issue was this file management point.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Feathers&lt;/em&gt; is a nice idea, but it require a &lt;em&gt;start from scratch&lt;/em&gt; regarding the backend, and the &lt;em&gt;react-admin&lt;/em&gt; solution was not so &lt;em&gt;magic&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;About &lt;em&gt;Back4app&lt;/em&gt; it is the first time I was dealing with the Parse system. I was worried to need to implement a specific Parse Client API inside my frontend to use it. The goal was not to rebuild completly the frontend application.&lt;/p&gt;

&lt;p&gt;Finally I decided to give a try with &lt;em&gt;Strapi.io&lt;/em&gt; and check if it fit my collaborators ! I know it is not perfect but it is very fast to setup and deploy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update 2020-04-22
&lt;/h2&gt;

&lt;p&gt;Finally I have decided to use the &lt;a href="https://marmelab.com/react-admin/" rel="noopener noreferrer"&gt;react-admin&lt;/a&gt; project in the frontend part:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It does not need to replace my existing Express API. It save me some time.&lt;/li&gt;
&lt;li&gt;It is more all-in-one place for all my users. Both end-users and admin-users can consume the same url, same application. It is globally easier.&lt;/li&gt;
&lt;li&gt;It seems pretty intimidating to create a custom &lt;a href="https://marmelab.com/react-admin/DataProviders.html#writing-your-own-data-provider" rel="noopener noreferrer"&gt;DataProvider&lt;/a&gt; for my existing API but the documentation of the project is accessible and I have managed to setup &lt;a href="https://github.com/julbrs/montessori-ressources/blob/8b374ed92e9dabead47b2434619bc7693626e0b8/src/components/Admin/dataprovider.js" rel="noopener noreferrer"&gt;mine&lt;/a&gt; in a couple of hours.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Let me know in comment what is your favorite admin-panel solution, and why you need one !&lt;/em&gt;&lt;/p&gt;

</description>
      <category>strapi</category>
      <category>node</category>
      <category>express</category>
    </item>
  </channel>
</rss>
