Jekyll2022-10-27T16:50:19+03:00/feed.xmlIzolabsIzolabs - a tech blog that contains tutorials and How-To's on both System Administration and Web Development.End to End Tests for our Storefront Backend2022-08-24T09:33:00+03:002022-08-24T09:33:00+03:00/2022/08/end-to-end-tests<div class="card seriesNote">
<p class="text-center">
This article is <strong>Part 10</strong> in a <strong>10 Part</strong> Series.
</p>
<ul class="list-group list-group-flush">
<li class="list-group-item"> Part 1 -
<a href="/2022/04/bootstrapping-nestjs-ubuntu">Bootstrapping a Nest.js Project on Ubuntu</a>
</li>
<li class="list-group-item"> Part 2 -
<a href="/2022/04/create-nestjs-migrations">Create Nest.js Migrations</a>
</li>
<li class="list-group-item"> Part 3 -
<a href="/2022/04/create-nestjs-crud-resources">Create Nest.js CRUD Resources</a>
</li>
<li class="list-group-item"> Part 4 -
<a href="/2022/06/create-nestjs-seeders">Create Nest.js Seeders</a>
</li>
<li class="list-group-item"> Part 5 -
<a href="/2022/07/customer-module">Create a Customer Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 6 -
<a href="/2022/07/auth-module">Nest.js Authentication</a>
</li>
<li class="list-group-item"> Part 7 -
<a href="/2022/07/products-module">Create a Products Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 8 -
<a href="/2022/07/stripe-module">Create a Stripe Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 9 -
<a href="/2022/08/orders-module">Create an Orders Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 10 -
This Article
</li>
</ul>
</div>
<p><br /></p>
<p>Lastly, for completeness, here are the end to end tests for our application endpoints. In case of any issues, you can refer to my Github repository <a href="https://github.com/imuchene/storefront-backend">here</a>.</p>
<h3 id="main-app-e2e-tests">Main app e2e tests</h3>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/test/app.e2e-spec.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
test/app.e2e-spec.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/test/app.e2e-spec.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/test/app.e2e-spec.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Test</span><span class="p">,</span> <span class="nx">TestingModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/testing</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">INestApplication</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">request</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">supertest</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AppModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./../src/app.module</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">AppController (e2e)</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">app</span><span class="p">:</span> <span class="nx">INestApplication</span><span class="p">;</span>
<span class="nx">beforeAll</span><span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">jest</span><span class="p">.</span><span class="nx">setTimeout</span><span class="p">(</span><span class="mi">30</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">);</span>
<span class="kd">const</span> <span class="na">moduleFixture</span><span class="p">:</span> <span class="nx">TestingModule</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">Test</span><span class="p">.</span><span class="nx">createTestingModule</span><span class="p">({</span>
<span class="na">imports</span><span class="p">:</span> <span class="p">[</span><span class="nx">AppModule</span><span class="p">],</span>
<span class="p">}).</span><span class="nx">compile</span><span class="p">();</span>
<span class="nx">app</span> <span class="o">=</span> <span class="nx">moduleFixture</span><span class="p">.</span><span class="nx">createNestApplication</span><span class="p">();</span>
<span class="k">await</span> <span class="nx">app</span><span class="p">.</span><span class="nx">init</span><span class="p">();</span>
<span class="p">});</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">/ (GET)</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">request</span><span class="p">(</span><span class="nx">app</span><span class="p">.</span><span class="nx">getHttpServer</span><span class="p">())</span>
<span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">)</span>
<span class="p">.</span><span class="nx">expect</span><span class="p">(</span><span class="mi">200</span><span class="p">)</span>
<span class="p">.</span><span class="nx">expect</span><span class="p">(</span><span class="dl">'</span><span class="s1">Hello World!</span><span class="dl">'</span><span class="p">);</span>
<span class="p">});</span>
<span class="nx">afterAll</span><span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">app</span><span class="p">.</span><span class="nx">close</span><span class="p">();</span>
<span class="p">});</span>
<span class="p">});</span></code></pre></figure>
<p>The main app e2e test mainly has some boilerplate code that comes by default when we generate our application.</p>
<p>When you run the tests they should pass:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm run <span class="nb">test</span>:e2e app
</code></pre></div></div>
<p>Output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> storefront-backend@0.0.1 <span class="nb">test</span>:e2e
<span class="o">></span> jest <span class="nt">--config</span> ./test/jest-e2e.json <span class="s2">"app"</span>
console.log
Before...
at LoggingInterceptor.intercept <span class="o">(</span>../src/common/interceptors/logging.interceptor.ts:13:13<span class="o">)</span>
console.log
After... 0ms
at Object.next <span class="o">(</span>../src/common/interceptors/logging.interceptor.ts:18:31<span class="o">)</span>
PASS <span class="nb">test</span>/app.e2e-spec.ts
AppController <span class="o">(</span>e2e<span class="o">)</span>
✓ / <span class="o">(</span>GET<span class="o">)</span> <span class="o">(</span>34 ms<span class="o">)</span>
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 4.004 s
Ran all <span class="nb">test </span>suites matching /app/i.
</code></pre></div></div>
<h3 id="auth-e2e-tests">Auth e2e tests</h3>
<p>We test our auth endpoints here. The describe blocks explain what the specs are meant to do:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/test/auth.e2e-spec.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
test/auth.e2e-spec.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/test/auth.e2e-spec.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/test/auth.e2e-spec.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Test</span><span class="p">,</span> <span class="nx">TestingModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/testing</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">INestApplication</span><span class="p">,</span> <span class="nx">ValidationPipe</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">request</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">supertest</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AppModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./../src/app.module</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Customer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../src/modules/customers/entities/customer.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">faker</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@faker-js/faker</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">cookieParser</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">cookie-parser</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">AuthController (e2e)</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">app</span><span class="p">:</span> <span class="nx">INestApplication</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">testPassword</span> <span class="o">=</span> <span class="nx">faker</span><span class="p">.</span><span class="nx">random</span><span class="p">.</span><span class="nx">alpha</span><span class="p">(</span><span class="mi">10</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">testFirstName</span> <span class="o">=</span> <span class="nx">faker</span><span class="p">.</span><span class="nx">name</span><span class="p">.</span><span class="nx">firstName</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">testLastName</span> <span class="o">=</span> <span class="nx">faker</span><span class="p">.</span><span class="nx">name</span><span class="p">.</span><span class="nx">lastName</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">fakeCustomer</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Customer</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="nx">testFirstName</span> <span class="o">+</span> <span class="dl">'</span><span class="s1"> </span><span class="dl">'</span> <span class="o">+</span> <span class="nx">testLastName</span><span class="p">,</span>
<span class="na">email</span><span class="p">:</span> <span class="s2">`</span><span class="p">${</span><span class="nx">testFirstName</span><span class="p">.</span><span class="nx">toLowerCase</span><span class="p">()}</span><span class="s2">@example.com`</span><span class="p">,</span>
<span class="na">phoneNumber</span><span class="p">:</span> <span class="nx">faker</span><span class="p">.</span><span class="nx">phone</span><span class="p">.</span><span class="nx">imei</span><span class="p">(),</span>
<span class="na">password</span><span class="p">:</span> <span class="nx">testPassword</span><span class="p">,</span>
<span class="na">confirmPassword</span><span class="p">:</span> <span class="nx">testPassword</span><span class="p">,</span>
<span class="p">});</span>
<span class="nx">beforeAll</span><span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">jest</span><span class="p">.</span><span class="nx">setTimeout</span><span class="p">(</span><span class="mi">30</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">);</span>
<span class="kd">const</span> <span class="na">moduleFixture</span><span class="p">:</span> <span class="nx">TestingModule</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">Test</span><span class="p">.</span><span class="nx">createTestingModule</span><span class="p">({</span>
<span class="na">imports</span><span class="p">:</span> <span class="p">[</span><span class="nx">AppModule</span><span class="p">],</span>
<span class="p">}).</span><span class="nx">compile</span><span class="p">();</span>
<span class="nx">app</span> <span class="o">=</span> <span class="nx">moduleFixture</span><span class="p">.</span><span class="nx">createNestApplication</span><span class="p">();</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">useGlobalPipes</span><span class="p">(</span><span class="k">new</span> <span class="nx">ValidationPipe</span><span class="p">());</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">cookieParser</span><span class="p">(</span><span class="dl">'</span><span class="s1">testString</span><span class="dl">'</span><span class="p">));</span>
<span class="k">await</span> <span class="nx">app</span><span class="p">.</span><span class="nx">init</span><span class="p">();</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">when registering</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">and using valid data</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should respond with the customer data minus the password</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">request</span><span class="p">(</span><span class="nx">app</span><span class="p">.</span><span class="nx">getHttpServer</span><span class="p">())</span>
<span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="dl">'</span><span class="s1">/auth/register</span><span class="dl">'</span><span class="p">)</span>
<span class="p">.</span><span class="nx">send</span><span class="p">({</span>
<span class="na">email</span><span class="p">:</span> <span class="nx">fakeCustomer</span><span class="p">.</span><span class="nx">email</span><span class="p">,</span>
<span class="na">name</span><span class="p">:</span> <span class="nx">fakeCustomer</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span>
<span class="na">phoneNumber</span><span class="p">:</span> <span class="nx">fakeCustomer</span><span class="p">.</span><span class="nx">phoneNumber</span><span class="p">,</span>
<span class="na">password</span><span class="p">:</span> <span class="nx">fakeCustomer</span><span class="p">.</span><span class="nx">password</span><span class="p">,</span>
<span class="na">confirmPassword</span><span class="p">:</span> <span class="nx">fakeCustomer</span><span class="p">.</span><span class="nx">confirmPassword</span><span class="p">,</span>
<span class="p">})</span>
<span class="p">.</span><span class="nx">expect</span><span class="p">(</span><span class="mi">201</span><span class="p">);</span>
<span class="p">});</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should throw an error when a duplicate user is registered</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">request</span><span class="p">(</span><span class="nx">app</span><span class="p">.</span><span class="nx">getHttpServer</span><span class="p">())</span>
<span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="dl">'</span><span class="s1">/auth/register</span><span class="dl">'</span><span class="p">)</span>
<span class="p">.</span><span class="nx">send</span><span class="p">({</span>
<span class="na">email</span><span class="p">:</span> <span class="nx">fakeCustomer</span><span class="p">.</span><span class="nx">email</span><span class="p">,</span>
<span class="na">name</span><span class="p">:</span> <span class="nx">fakeCustomer</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span>
<span class="na">phoneNumber</span><span class="p">:</span> <span class="nx">fakeCustomer</span><span class="p">.</span><span class="nx">phoneNumber</span><span class="p">,</span>
<span class="na">password</span><span class="p">:</span> <span class="nx">fakeCustomer</span><span class="p">.</span><span class="nx">password</span><span class="p">,</span>
<span class="na">confirmPassword</span><span class="p">:</span> <span class="nx">fakeCustomer</span><span class="p">.</span><span class="nx">confirmPassword</span><span class="p">,</span>
<span class="p">})</span>
<span class="p">.</span><span class="nx">expect</span><span class="p">(</span><span class="mi">400</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">and using invalid data</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should throw an error</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">request</span><span class="p">(</span><span class="nx">app</span><span class="p">.</span><span class="nx">getHttpServer</span><span class="p">())</span>
<span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="dl">'</span><span class="s1">/auth/register</span><span class="dl">'</span><span class="p">)</span>
<span class="p">.</span><span class="nx">send</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="nx">fakeCustomer</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span>
<span class="p">})</span>
<span class="p">.</span><span class="nx">expect</span><span class="p">(</span><span class="mi">400</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">when logging in</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">and using valid data</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should respond with a success message</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">request</span><span class="p">(</span><span class="nx">app</span><span class="p">.</span><span class="nx">getHttpServer</span><span class="p">())</span>
<span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="dl">'</span><span class="s1">/auth/login</span><span class="dl">'</span><span class="p">)</span>
<span class="p">.</span><span class="nx">send</span><span class="p">({</span>
<span class="na">email</span><span class="p">:</span> <span class="nx">fakeCustomer</span><span class="p">.</span><span class="nx">email</span><span class="p">,</span>
<span class="na">password</span><span class="p">:</span> <span class="nx">fakeCustomer</span><span class="p">.</span><span class="nx">password</span><span class="p">,</span>
<span class="p">})</span>
<span class="p">.</span><span class="nx">expect</span><span class="p">(</span><span class="mi">201</span><span class="p">)</span>
<span class="p">.</span><span class="nx">expect</span><span class="p">({</span>
<span class="na">msg</span><span class="p">:</span> <span class="dl">'</span><span class="s1">success</span><span class="dl">'</span><span class="p">,</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">and using invalid data</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should respond with a bad request error message</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">request</span><span class="p">(</span><span class="nx">app</span><span class="p">.</span><span class="nx">getHttpServer</span><span class="p">())</span>
<span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="dl">'</span><span class="s1">/auth/login</span><span class="dl">'</span><span class="p">)</span>
<span class="p">.</span><span class="nx">send</span><span class="p">({</span>
<span class="na">email</span><span class="p">:</span> <span class="dl">'</span><span class="s1">fake@fake.com</span><span class="dl">'</span><span class="p">,</span>
<span class="na">password</span><span class="p">:</span> <span class="dl">'</span><span class="s1">fakepassword</span><span class="dl">'</span><span class="p">,</span>
<span class="p">})</span>
<span class="p">.</span><span class="nx">expect</span><span class="p">(</span><span class="mi">400</span><span class="p">)</span>
<span class="p">.</span><span class="nx">expect</span><span class="p">({</span>
<span class="na">statusCode</span><span class="p">:</span> <span class="mi">400</span><span class="p">,</span>
<span class="na">message</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Wrong credentials provided</span><span class="dl">'</span><span class="p">,</span>
<span class="na">error</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Bad Request</span><span class="dl">'</span><span class="p">,</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">when logging out</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">and using an invalid cookie</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should respond with an unauthorized message</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">request</span><span class="p">(</span><span class="nx">app</span><span class="p">.</span><span class="nx">getHttpServer</span><span class="p">()).</span><span class="k">delete</span><span class="p">(</span><span class="dl">'</span><span class="s1">/auth/log_out</span><span class="dl">'</span><span class="p">).</span><span class="nx">expect</span><span class="p">(</span><span class="mi">401</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">when getting a refresh token</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">without a valid jwt token</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should respond with an unauthorized message</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">request</span><span class="p">(</span><span class="nx">app</span><span class="p">.</span><span class="nx">getHttpServer</span><span class="p">())</span>
<span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/auth/refresh_token</span><span class="dl">'</span><span class="p">)</span>
<span class="p">.</span><span class="nx">expect</span><span class="p">(</span><span class="mi">401</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">afterAll</span><span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">app</span><span class="p">.</span><span class="nx">close</span><span class="p">();</span>
<span class="p">});</span>
<span class="p">});</span></code></pre></figure>
<p>These should also pass when run:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm run <span class="nb">test</span>:e2e auth
</code></pre></div></div>
<p>Output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> storefront-backend@0.0.1 <span class="nb">test</span>:e2e
<span class="o">></span> jest <span class="nt">--config</span> ./test/jest-e2e.json <span class="s2">"auth"</span>
console.log
Before...
at LoggingInterceptor.intercept <span class="o">(</span>../src/common/interceptors/logging.interceptor.ts:13:13<span class="o">)</span>
console.log
After... 64ms
at Object.next <span class="o">(</span>../src/common/interceptors/logging.interceptor.ts:18:31<span class="o">)</span>
console.log
Before...
at LoggingInterceptor.intercept <span class="o">(</span>../src/common/interceptors/logging.interceptor.ts:13:13<span class="o">)</span>
console.log
Before...
at LoggingInterceptor.intercept <span class="o">(</span>../src/common/interceptors/logging.interceptor.ts:13:13<span class="o">)</span>
console.log
Before...
at LoggingInterceptor.intercept <span class="o">(</span>../src/common/interceptors/logging.interceptor.ts:13:13<span class="o">)</span>
console.log
After... 58ms
at Object.next <span class="o">(</span>../src/common/interceptors/logging.interceptor.ts:18:31<span class="o">)</span>
PASS <span class="nb">test</span>/auth.e2e-spec.ts
AuthController <span class="o">(</span>e2e<span class="o">)</span>
when registering
and using valid data
✓ should respond with the customer data minus the password <span class="o">(</span>103 ms<span class="o">)</span>
✓ should throw an error when a duplicate user is registered <span class="o">(</span>55 ms<span class="o">)</span>
and using invalid data
✓ should throw an error <span class="o">(</span>3 ms<span class="o">)</span>
when logging <span class="k">in
</span>and using valid data
✓ should respond with a success message <span class="o">(</span>116 ms<span class="o">)</span>
and using invalid data
✓ should respond with a bad request error message <span class="o">(</span>3 ms<span class="o">)</span>
when logging out
and using an invalid cookie
✓ should respond with an unauthorized message <span class="o">(</span>2 ms<span class="o">)</span>
when getting a refresh token
without a valid jwt token
✓ should respond with an unauthorized message <span class="o">(</span>2 ms<span class="o">)</span>
Test Suites: 1 passed, 1 total
Tests: 7 passed, 7 total
Snapshots: 0 total
Time: 4.869 s
Ran all <span class="nb">test </span>suites matching /auth/i.
</code></pre></div></div>
<h3 id="orders-e2e-tests">Orders e2e tests</h3>
<p>Here we start by mocking an auth guard as well as the expected results before calling the order controller endpoints:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/test/orders.e2e-spec.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
test/orders.e2e-spec.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/test/orders.e2e-spec.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/test/orders.e2e-spec.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Test</span><span class="p">,</span> <span class="nx">TestingModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/testing</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CanActivate</span><span class="p">,</span> <span class="nx">INestApplication</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">request</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">supertest</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AppModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../src/app.module</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">JwtAuthGuard</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../src/modules/auth/guards/jwt-auth.guard</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">cookieParser</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">cookie-parser</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">OrdersService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../src/modules/orders/orders.service</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">OrdersController (e2e)</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">app</span><span class="p">:</span> <span class="nx">INestApplication</span><span class="p">;</span>
<span class="kd">const</span> <span class="na">mockAuthGuard</span><span class="p">:</span> <span class="nx">CanActivate</span> <span class="o">=</span> <span class="p">{</span> <span class="na">canActivate</span><span class="p">:</span> <span class="p">()</span> <span class="o">=></span> <span class="kc">true</span> <span class="p">};</span>
<span class="kd">const</span> <span class="nx">mockOrdersService</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">create</span><span class="p">:</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">customerId</span><span class="p">:</span> <span class="dl">'</span><span class="s1">1660be29-204e-4bfa-a68e-23ecb042d8c3</span><span class="dl">'</span><span class="p">,</span>
<span class="na">totalAmount</span><span class="p">:</span> <span class="mf">55.25</span><span class="p">,</span>
<span class="na">orderItems</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="na">productId</span><span class="p">:</span> <span class="dl">'</span><span class="s1">2f3b4332-1189-4a95-aed1-d8a243e7bacb</span><span class="dl">'</span><span class="p">,</span>
<span class="na">quantity</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
<span class="na">unitPrice</span><span class="p">:</span> <span class="mf">12.13</span><span class="p">,</span>
<span class="na">orderId</span><span class="p">:</span> <span class="dl">'</span><span class="s1">374eb139-cd27-4829-aeb1-220844fc0414</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">productId</span><span class="p">:</span> <span class="dl">'</span><span class="s1">43efaa10-ffc2-4c24-a05b-5aef6237b5c1</span><span class="dl">'</span><span class="p">,</span>
<span class="na">quantity</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="na">unitPrice</span><span class="p">:</span> <span class="mf">10.33</span><span class="p">,</span>
<span class="na">orderId</span><span class="p">:</span> <span class="dl">'</span><span class="s1">374eb139-cd27-4829-aeb1-220844fc0414</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">],</span>
<span class="na">id</span><span class="p">:</span> <span class="dl">'</span><span class="s1">374eb139-cd27-4829-aeb1-220844fc0413</span><span class="dl">'</span><span class="p">,</span>
<span class="na">paymentStatus</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Created</span><span class="dl">'</span><span class="p">,</span>
<span class="na">createdAt</span><span class="p">:</span> <span class="dl">'</span><span class="s1">2022-06-07T14:10:49.101Z</span><span class="dl">'</span><span class="p">,</span>
<span class="na">updatedAt</span><span class="p">:</span> <span class="dl">'</span><span class="s1">2022-06-07T14:10:49.101Z</span><span class="dl">'</span><span class="p">,</span>
<span class="na">clientSecret</span><span class="p">:</span> <span class="dl">'</span><span class="s1">pi_test_client_secret</span><span class="dl">'</span><span class="p">,</span>
<span class="p">};</span>
<span class="p">},</span>
<span class="na">updatePaymentStatus</span><span class="p">:</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span> <span class="na">message</span><span class="p">:</span> <span class="dl">'</span><span class="s1">success</span><span class="dl">'</span> <span class="p">};</span>
<span class="p">},</span>
<span class="p">};</span>
<span class="nx">beforeAll</span><span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">jest</span><span class="p">.</span><span class="nx">setTimeout</span><span class="p">(</span><span class="mi">30</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">);</span>
<span class="kd">const</span> <span class="na">moduleFixture</span><span class="p">:</span> <span class="nx">TestingModule</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">Test</span><span class="p">.</span><span class="nx">createTestingModule</span><span class="p">({</span>
<span class="na">imports</span><span class="p">:</span> <span class="p">[</span><span class="nx">AppModule</span><span class="p">],</span>
<span class="p">})</span>
<span class="p">.</span><span class="nx">overrideGuard</span><span class="p">(</span><span class="nx">JwtAuthGuard</span><span class="p">)</span>
<span class="p">.</span><span class="nx">useValue</span><span class="p">(</span><span class="nx">mockAuthGuard</span><span class="p">)</span>
<span class="p">.</span><span class="nx">overrideProvider</span><span class="p">(</span><span class="nx">OrdersService</span><span class="p">)</span>
<span class="p">.</span><span class="nx">useValue</span><span class="p">(</span><span class="nx">mockOrdersService</span><span class="p">)</span>
<span class="p">.</span><span class="nx">compile</span><span class="p">();</span>
<span class="nx">app</span> <span class="o">=</span> <span class="nx">moduleFixture</span><span class="p">.</span><span class="nx">createNestApplication</span><span class="p">();</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">cookieParser</span><span class="p">(</span><span class="dl">'</span><span class="s1">testString</span><span class="dl">'</span><span class="p">));</span>
<span class="k">await</span> <span class="nx">app</span><span class="p">.</span><span class="nx">init</span><span class="p">();</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">order creation</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">returns an order object</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">request</span><span class="p">(</span><span class="nx">app</span><span class="p">.</span><span class="nx">getHttpServer</span><span class="p">())</span>
<span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="dl">'</span><span class="s1">/orders</span><span class="dl">'</span><span class="p">)</span>
<span class="p">.</span><span class="nx">expect</span><span class="p">(</span><span class="mi">201</span><span class="p">)</span>
<span class="p">.</span><span class="nx">expect</span><span class="p">(</span><span class="nx">mockOrdersService</span><span class="p">.</span><span class="nx">create</span><span class="p">());</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">stripe callback</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">updates the order payment status via the webhook</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">request</span><span class="p">(</span><span class="nx">app</span><span class="p">.</span><span class="nx">getHttpServer</span><span class="p">())</span>
<span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="dl">'</span><span class="s1">/orders/stripe_webhook</span><span class="dl">'</span><span class="p">)</span>
<span class="p">.</span><span class="nx">expect</span><span class="p">(</span><span class="mi">201</span><span class="p">)</span>
<span class="p">.</span><span class="nx">expect</span><span class="p">(</span><span class="nx">mockOrdersService</span><span class="p">.</span><span class="nx">updatePaymentStatus</span><span class="p">());</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">afterAll</span><span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">app</span><span class="p">.</span><span class="nx">close</span><span class="p">();</span>
<span class="p">});</span>
<span class="p">});</span></code></pre></figure>
<p>Run the spec:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm run <span class="nb">test</span>:e2e orders
</code></pre></div></div>
<p>Output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> storefront-backend@0.0.1 <span class="nb">test</span>:e2e
<span class="o">></span> jest <span class="nt">--config</span> ./test/jest-e2e.json <span class="s2">"orders"</span>
console.log
Before...
at LoggingInterceptor.intercept <span class="o">(</span>../src/common/interceptors/logging.interceptor.ts:13:13<span class="o">)</span>
console.log
After... 1ms
at Object.next <span class="o">(</span>../src/common/interceptors/logging.interceptor.ts:18:31<span class="o">)</span>
console.log
Before...
at LoggingInterceptor.intercept <span class="o">(</span>../src/common/interceptors/logging.interceptor.ts:13:13<span class="o">)</span>
console.log
After... 0ms
at Object.next <span class="o">(</span>../src/common/interceptors/logging.interceptor.ts:18:31<span class="o">)</span>
PASS <span class="nb">test</span>/orders.e2e-spec.ts
OrdersController <span class="o">(</span>e2e<span class="o">)</span>
order creation
✓ returns an order object <span class="o">(</span>34 ms<span class="o">)</span>
stripe callback
✓ updates the order payment status via the webhook <span class="o">(</span>4 ms<span class="o">)</span>
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 3.638 s
Ran all <span class="nb">test </span>suites matching /orders/i.
</code></pre></div></div>
<h3 id="products-e2e-tests">Products e2e tests</h3>
<p>Lastly we test our products controller endpoints, using mocks where necessary:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/test/products.e2e-spec.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
test/products.e2e-spec.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/test/products.e2e-spec.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/test/products.e2e-spec.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Test</span><span class="p">,</span> <span class="nx">TestingModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/testing</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CanActivate</span><span class="p">,</span> <span class="nx">INestApplication</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">request</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">supertest</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AppModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../src/app.module</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">JwtAuthGuard</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../src/modules/auth/guards/jwt-auth.guard</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">cookieParser</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">cookie-parser</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ProductsService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../src/modules/products/products.service</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">DeleteResult</span><span class="p">,</span> <span class="nx">UpdateResult</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">ProductsController (e2e)</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">app</span><span class="p">:</span> <span class="nx">INestApplication</span><span class="p">;</span>
<span class="kd">const</span> <span class="na">mockAuthGuard</span><span class="p">:</span> <span class="nx">CanActivate</span> <span class="o">=</span> <span class="p">{</span> <span class="na">canActivate</span><span class="p">:</span> <span class="p">()</span> <span class="o">=></span> <span class="kc">true</span> <span class="p">};</span>
<span class="kd">const</span> <span class="nx">mockProductObject</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Test Product 03</span><span class="dl">'</span><span class="p">,</span>
<span class="na">unitPrice</span><span class="p">:</span> <span class="mf">10.13</span><span class="p">,</span>
<span class="na">description</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Test Product 03</span><span class="dl">'</span><span class="p">,</span>
<span class="na">image</span><span class="p">:</span> <span class="dl">'</span><span class="s1">images/</span><span class="dl">'</span><span class="p">,</span>
<span class="na">deletedAt</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="na">id</span><span class="p">:</span> <span class="dl">'</span><span class="s1">10335531-df60-4c24-9597-8ce13d841929</span><span class="dl">'</span><span class="p">,</span>
<span class="na">createdAt</span><span class="p">:</span> <span class="dl">'</span><span class="s1">2022-06-08T13:57:28.247Z</span><span class="dl">'</span><span class="p">,</span>
<span class="na">updatedAt</span><span class="p">:</span> <span class="dl">'</span><span class="s1">2022-06-08T13:57:28.247Z</span><span class="dl">'</span><span class="p">,</span>
<span class="p">};</span>
<span class="kd">const</span> <span class="na">mockTypeormResult</span><span class="p">:</span> <span class="nx">UpdateResult</span> <span class="o">|</span> <span class="nx">DeleteResult</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">generatedMaps</span><span class="p">:</span> <span class="p">[],</span>
<span class="na">raw</span><span class="p">:</span> <span class="p">[],</span>
<span class="na">affected</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="p">};</span>
<span class="kd">const</span> <span class="nx">mockProductsService</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">create</span><span class="p">:</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">mockProductObject</span><span class="p">;</span>
<span class="p">},</span>
<span class="na">findOne</span><span class="p">:</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">mockProductObject</span><span class="p">;</span>
<span class="p">},</span>
<span class="na">findAll</span><span class="p">:</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="p">[</span><span class="nx">mockProductObject</span><span class="p">,</span> <span class="nx">mockProductObject</span><span class="p">];</span>
<span class="p">},</span>
<span class="na">update</span><span class="p">:</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">mockTypeormResult</span><span class="p">;</span>
<span class="p">},</span>
<span class="na">remove</span><span class="p">:</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">mockTypeormResult</span><span class="p">;</span>
<span class="p">},</span>
<span class="p">};</span>
<span class="nx">beforeAll</span><span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">jest</span><span class="p">.</span><span class="nx">setTimeout</span><span class="p">(</span><span class="mi">30</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">);</span>
<span class="kd">const</span> <span class="na">moduleFixture</span><span class="p">:</span> <span class="nx">TestingModule</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">Test</span><span class="p">.</span><span class="nx">createTestingModule</span><span class="p">({</span>
<span class="na">imports</span><span class="p">:</span> <span class="p">[</span><span class="nx">AppModule</span><span class="p">],</span>
<span class="p">})</span>
<span class="p">.</span><span class="nx">overrideGuard</span><span class="p">(</span><span class="nx">JwtAuthGuard</span><span class="p">)</span>
<span class="p">.</span><span class="nx">useValue</span><span class="p">(</span><span class="nx">mockAuthGuard</span><span class="p">)</span>
<span class="p">.</span><span class="nx">overrideProvider</span><span class="p">(</span><span class="nx">ProductsService</span><span class="p">)</span>
<span class="p">.</span><span class="nx">useValue</span><span class="p">(</span><span class="nx">mockProductsService</span><span class="p">)</span>
<span class="p">.</span><span class="nx">compile</span><span class="p">();</span>
<span class="nx">app</span> <span class="o">=</span> <span class="nx">moduleFixture</span><span class="p">.</span><span class="nx">createNestApplication</span><span class="p">();</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">cookieParser</span><span class="p">(</span><span class="dl">'</span><span class="s1">testString</span><span class="dl">'</span><span class="p">));</span>
<span class="k">await</span> <span class="nx">app</span><span class="p">.</span><span class="nx">init</span><span class="p">();</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">product creation</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">returns a product object</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">request</span><span class="p">(</span><span class="nx">app</span><span class="p">.</span><span class="nx">getHttpServer</span><span class="p">())</span>
<span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="dl">'</span><span class="s1">/products</span><span class="dl">'</span><span class="p">)</span>
<span class="p">.</span><span class="nx">expect</span><span class="p">(</span><span class="mi">201</span><span class="p">)</span>
<span class="p">.</span><span class="nx">expect</span><span class="p">(</span><span class="nx">mockProductsService</span><span class="p">.</span><span class="nx">create</span><span class="p">());</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">find all products</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">returns an array of products</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">request</span><span class="p">(</span><span class="nx">app</span><span class="p">.</span><span class="nx">getHttpServer</span><span class="p">())</span>
<span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/products</span><span class="dl">'</span><span class="p">)</span>
<span class="p">.</span><span class="nx">expect</span><span class="p">(</span><span class="mi">200</span><span class="p">)</span>
<span class="p">.</span><span class="nx">expect</span><span class="p">(</span><span class="nx">mockProductsService</span><span class="p">.</span><span class="nx">findAll</span><span class="p">());</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">updating a product</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">returns an update result</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">request</span><span class="p">(</span><span class="nx">app</span><span class="p">.</span><span class="nx">getHttpServer</span><span class="p">())</span>
<span class="p">.</span><span class="nx">patch</span><span class="p">(</span><span class="s2">`/products/</span><span class="p">${</span><span class="nx">mockProductObject</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
<span class="p">.</span><span class="nx">send</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Berries</span><span class="dl">'</span> <span class="p">})</span>
<span class="p">.</span><span class="nx">expect</span><span class="p">(</span><span class="mi">200</span><span class="p">)</span>
<span class="p">.</span><span class="nx">expect</span><span class="p">(</span><span class="nx">mockProductsService</span><span class="p">.</span><span class="nx">update</span><span class="p">());</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">deleting a product</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">returns a delete result</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">request</span><span class="p">(</span><span class="nx">app</span><span class="p">.</span><span class="nx">getHttpServer</span><span class="p">())</span>
<span class="p">.</span><span class="k">delete</span><span class="p">(</span><span class="s2">`/products/</span><span class="p">${</span><span class="nx">mockProductObject</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
<span class="p">.</span><span class="nx">expect</span><span class="p">(</span><span class="mi">200</span><span class="p">)</span>
<span class="p">.</span><span class="nx">expect</span><span class="p">(</span><span class="nx">mockProductsService</span><span class="p">.</span><span class="nx">remove</span><span class="p">());</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">afterAll</span><span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">app</span><span class="p">.</span><span class="nx">close</span><span class="p">();</span>
<span class="p">});</span>
<span class="p">});</span></code></pre></figure>
<p>Run the spec:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm run <span class="nb">test</span>:e2e products
</code></pre></div></div>
<p>Output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> storefront-backend@0.0.1 <span class="nb">test</span>:e2e
<span class="o">></span> jest <span class="nt">--config</span> ./test/jest-e2e.json <span class="s2">"products"</span>
console.log
Before...
at LoggingInterceptor.intercept <span class="o">(</span>../src/common/interceptors/logging.interceptor.ts:13:13<span class="o">)</span>
console.log
After... 0ms
at Object.next <span class="o">(</span>../src/common/interceptors/logging.interceptor.ts:18:31<span class="o">)</span>
console.log
Before...
at LoggingInterceptor.intercept <span class="o">(</span>../src/common/interceptors/logging.interceptor.ts:13:13<span class="o">)</span>
console.log
After... 1ms
at Object.next <span class="o">(</span>../src/common/interceptors/logging.interceptor.ts:18:31<span class="o">)</span>
console.log
Before...
at LoggingInterceptor.intercept <span class="o">(</span>../src/common/interceptors/logging.interceptor.ts:13:13<span class="o">)</span>
console.log
After... 0ms
at Object.next <span class="o">(</span>../src/common/interceptors/logging.interceptor.ts:18:31<span class="o">)</span>
console.log
Before...
at LoggingInterceptor.intercept <span class="o">(</span>../src/common/interceptors/logging.interceptor.ts:13:13<span class="o">)</span>
console.log
After... 0ms
at Object.next <span class="o">(</span>../src/common/interceptors/logging.interceptor.ts:18:31<span class="o">)</span>
PASS <span class="nb">test</span>/products.e2e-spec.ts
ProductsController <span class="o">(</span>e2e<span class="o">)</span>
product creation
✓ returns a product object <span class="o">(</span>98 ms<span class="o">)</span>
find all products
✓ returns an array of products <span class="o">(</span>5 ms<span class="o">)</span>
updating a product
✓ returns an update result <span class="o">(</span>8 ms<span class="o">)</span>
deleting a product
✓ returns a delete result <span class="o">(</span>3 ms<span class="o">)</span>
Test Suites: 1 passed, 1 total
Tests: 4 passed, 4 total
Snapshots: 0 total
Time: 3.606 s
Ran all <span class="nb">test </span>suites matching /products/i.
</code></pre></div></div>Isaiah MucheneLastly, for completeness, here are the end to end tests for our application endpoints. In case of any issues, you can refer to my Github repositoryCreate an Orders Module in Nest.js2022-08-23T16:01:00+03:002022-08-23T16:01:00+03:00/2022/08/orders-module<div class="card seriesNote">
<p class="text-center">
This article is <strong>Part 9</strong> in a <strong>10 Part</strong> Series.
</p>
<ul class="list-group list-group-flush">
<li class="list-group-item"> Part 1 -
<a href="/2022/04/bootstrapping-nestjs-ubuntu">Bootstrapping a Nest.js Project on Ubuntu</a>
</li>
<li class="list-group-item"> Part 2 -
<a href="/2022/04/create-nestjs-migrations">Create Nest.js Migrations</a>
</li>
<li class="list-group-item"> Part 3 -
<a href="/2022/04/create-nestjs-crud-resources">Create Nest.js CRUD Resources</a>
</li>
<li class="list-group-item"> Part 4 -
<a href="/2022/06/create-nestjs-seeders">Create Nest.js Seeders</a>
</li>
<li class="list-group-item"> Part 5 -
<a href="/2022/07/customer-module">Create a Customer Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 6 -
<a href="/2022/07/auth-module">Nest.js Authentication</a>
</li>
<li class="list-group-item"> Part 7 -
<a href="/2022/07/products-module">Create a Products Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 8 -
<a href="/2022/07/stripe-module">Create a Stripe Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 9 -
This Article
</li>
<li class="list-group-item"> Part 10 -
<a href="/2022/08/end-to-end-tests">End to End Tests for our Storefront Backend</a>
</li>
</ul>
</div>
<p><br /></p>
<p>Today, we shall be working on the orders module. This module ties in logic from all other modules, and is where the bulk of the order processing will be done. In case of any issues, you can refer to my Github repository <a href="https://github.com/imuchene/storefront-backend">here</a>.</p>
<h3 id="orders-module">Orders Module</h3>
<p>Here’s how the actual module file should look like. Nothing fancy here, mostly importing functionality from previous modules:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/orders.module.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/orders/orders.module.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/orders.module.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/orders.module.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Module</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">OrdersService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./orders.service</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">OrdersController</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./orders.controller</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">TypeOrmModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Order</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./entities/order.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ProductsModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../products/products.module</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">StripeModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../stripe/stripe.module</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Module</span><span class="p">({</span>
<span class="na">imports</span><span class="p">:</span> <span class="p">[</span><span class="nx">TypeOrmModule</span><span class="p">.</span><span class="nx">forFeature</span><span class="p">([</span><span class="nx">Order</span><span class="p">]),</span> <span class="nx">ProductsModule</span><span class="p">,</span> <span class="nx">StripeModule</span><span class="p">],</span>
<span class="na">controllers</span><span class="p">:</span> <span class="p">[</span><span class="nx">OrdersController</span><span class="p">],</span>
<span class="na">providers</span><span class="p">:</span> <span class="p">[</span><span class="nx">OrdersService</span><span class="p">],</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">OrdersModule</span> <span class="p">{}</span></code></pre></figure>
<h3 id="orders-entity">Orders Entity</h3>
<p>There are only a few changes to the order entity. Don’t forget to add the <strong>paymentStatus</strong> attribute, with its corresponding properties:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/entities/order.entity.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/orders/entities/order.entity.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/entities/order.entity.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/entities/order.entity.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Customer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../customers/entities/customer.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span>
<span class="nx">Column</span><span class="p">,</span>
<span class="nx">CreateDateColumn</span><span class="p">,</span>
<span class="nx">Entity</span><span class="p">,</span>
<span class="nx">JoinColumn</span><span class="p">,</span>
<span class="nx">ManyToOne</span><span class="p">,</span>
<span class="nx">OneToMany</span><span class="p">,</span>
<span class="nx">PrimaryGeneratedColumn</span><span class="p">,</span>
<span class="nx">UpdateDateColumn</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">OrderItem</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./order-item.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">PaymentStatus</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../../common/enums/payment-status.enum</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Entity</span><span class="p">(</span><span class="dl">'</span><span class="s1">orders</span><span class="dl">'</span><span class="p">)</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">Order</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span><span class="nx">intialData</span><span class="p">:</span> <span class="nb">Partial</span><span class="o"><</span><span class="nx">Order</span><span class="o">></span> <span class="o">=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">intialData</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="nb">Object</span><span class="p">.</span><span class="nx">assign</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="nx">intialData</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">@</span><span class="nd">PrimaryGeneratedColumn</span><span class="p">(</span><span class="dl">'</span><span class="s1">uuid</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">id</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Column</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">customer_id</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">uuid</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">customerId</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Column</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">total_amount</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">numeric</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">totalAmount</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Column</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">payment_status</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">varchar</span><span class="dl">'</span><span class="p">,</span>
<span class="na">default</span><span class="p">:</span> <span class="nx">PaymentStatus</span><span class="p">.</span><span class="nx">Created</span><span class="p">,</span>
<span class="p">})</span>
<span class="nx">paymentStatus</span><span class="p">:</span> <span class="nx">PaymentStatus</span><span class="p">;</span>
<span class="p">@</span><span class="nd">CreateDateColumn</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">created_at</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">timestamptz</span><span class="dl">'</span><span class="p">,</span>
<span class="na">default</span><span class="p">:</span> <span class="dl">'</span><span class="s1">now()</span><span class="dl">'</span><span class="p">,</span>
<span class="na">readonly</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="p">})</span>
<span class="nx">createdAt</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">UpdateDateColumn</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">updated_at</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">timestamptz</span><span class="dl">'</span><span class="p">,</span>
<span class="na">default</span><span class="p">:</span> <span class="dl">'</span><span class="s1">now()</span><span class="dl">'</span><span class="p">,</span>
<span class="p">})</span>
<span class="nx">updatedAt</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">ManyToOne</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">Customer</span><span class="p">,</span> <span class="p">(</span><span class="nx">customer</span><span class="p">)</span> <span class="o">=></span> <span class="nx">customer</span><span class="p">.</span><span class="nx">orders</span><span class="p">)</span>
<span class="p">@</span><span class="nd">JoinColumn</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">customer_id</span><span class="dl">'</span><span class="p">,</span> <span class="na">referencedColumnName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">id</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">customer</span><span class="p">:</span> <span class="nx">Customer</span><span class="p">;</span>
<span class="p">@</span><span class="nd">OneToMany</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">OrderItem</span><span class="p">,</span> <span class="p">(</span><span class="nx">orderItem</span><span class="p">)</span> <span class="o">=></span> <span class="nx">orderItem</span><span class="p">.</span><span class="nx">order</span><span class="p">,</span> <span class="p">{</span> <span class="na">cascade</span><span class="p">:</span> <span class="kc">true</span> <span class="p">})</span>
<span class="nx">orderItems</span><span class="p">:</span> <span class="nx">OrderItem</span><span class="p">[];</span>
<span class="p">}</span></code></pre></figure>
<h3 id="order-items-entity">Order Items Entity</h3>
<p>Our order item entity is similar to the one in previous tutorials:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/entities/order-item.entity.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/orders/entities/order-item.entity.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/entities/order-item.entity.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/entities/order-item.entity.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Product</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../products/entities/product.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Column</span><span class="p">,</span> <span class="nx">Entity</span><span class="p">,</span> <span class="nx">JoinColumn</span><span class="p">,</span> <span class="nx">ManyToOne</span><span class="p">,</span> <span class="nx">PrimaryColumn</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Order</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./order.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Entity</span><span class="p">(</span><span class="dl">'</span><span class="s1">order_items</span><span class="dl">'</span><span class="p">)</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">OrderItem</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span><span class="nx">intialData</span><span class="p">:</span> <span class="nb">Partial</span><span class="o"><</span><span class="nx">OrderItem</span><span class="o">></span> <span class="o">=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">intialData</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="nb">Object</span><span class="p">.</span><span class="nx">assign</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="nx">intialData</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">@</span><span class="nd">PrimaryColumn</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">order_id</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">uuid</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">orderId</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">PrimaryColumn</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">product_id</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">uuid</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">productId</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Column</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">unit_price</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">numeric</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">unitPrice</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Column</span><span class="p">({</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">numeric</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">quantity</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">@</span><span class="nd">ManyToOne</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">Product</span><span class="p">,</span> <span class="p">(</span><span class="nx">product</span><span class="p">)</span> <span class="o">=></span> <span class="nx">product</span><span class="p">.</span><span class="nx">orderItems</span><span class="p">)</span>
<span class="p">@</span><span class="nd">JoinColumn</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">product_id</span><span class="dl">'</span><span class="p">,</span> <span class="na">referencedColumnName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">id</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">product</span><span class="p">:</span> <span class="nx">Product</span><span class="p">;</span>
<span class="p">@</span><span class="nd">ManyToOne</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">Order</span><span class="p">,</span> <span class="p">(</span><span class="nx">order</span><span class="p">)</span> <span class="o">=></span> <span class="nx">order</span><span class="p">.</span><span class="nx">orderItems</span><span class="p">)</span>
<span class="p">@</span><span class="nd">JoinColumn</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">order_id</span><span class="dl">'</span><span class="p">,</span> <span class="na">referencedColumnName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">id</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">order</span><span class="p">:</span> <span class="nx">Order</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<h3 id="create-order-dto">Create Order DTO</h3>
<p>Onto our DTOs. We’ll first start with the create order DTO:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/dto/create-order.dto.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/orders/dto/create-order.dto.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/dto/create-order.dto.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/dto/create-order.dto.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">IsNotEmpty</span><span class="p">,</span> <span class="nx">IsNumber</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">class-validator</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">OrderItem</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../entities/order-item.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">CreateOrderDto</span> <span class="p">{</span>
<span class="p">@</span><span class="nd">IsNumber</span><span class="p">()</span>
<span class="p">@</span><span class="nd">IsNotEmpty</span><span class="p">()</span>
<span class="nx">totalAmount</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">@</span><span class="nd">IsNotEmpty</span><span class="p">()</span>
<span class="nx">orderItems</span><span class="p">:</span> <span class="nx">OrderItem</span><span class="p">[];</span>
<span class="p">}</span></code></pre></figure>
<p>We’re basically making sure that in the request body for creating a new order, we are receiving the <strong>totalAmount</strong> as a string, and <strong>orderItems</strong> as an array.</p>
<h3 id="update-order-dto">Update Order DTO</h3>
<p>Here’s the update order DTO:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/dto/update-order.dto.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/orders/dto/update-order.dto.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/dto/update-order.dto.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/dto/update-order.dto.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">PartialType</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/mapped-types</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CreateOrderDto</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./create-order.dto</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">UpdateOrderDto</span> <span class="kd">extends</span> <span class="nx">PartialType</span><span class="p">(</span><span class="nx">CreateOrderDto</span><span class="p">)</span> <span class="p">{}</span></code></pre></figure>
<p>It inherits properties from the create order DTO.</p>
<h3 id="order-enums">Order Enums</h3>
<p>We’ll create a couple of enumerable classes to contain our payment statuses and payment intent event statuses:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/common/enums/payment-status.enum.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/common/enums/payment-status.enum.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/common/enums/payment-status.enum.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/common/enums/payment-status.enum.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">export</span> <span class="kr">enum</span> <span class="nx">PaymentStatus</span> <span class="p">{</span>
<span class="nx">Created</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">Created</span><span class="dl">'</span><span class="p">,</span>
<span class="nx">Processing</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">Processing</span><span class="dl">'</span><span class="p">,</span>
<span class="nx">Succeeded</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">Succeeded</span><span class="dl">'</span><span class="p">,</span>
<span class="nx">Failed</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">Failed</span><span class="dl">'</span><span class="p">,</span>
<span class="p">}</span></code></pre></figure>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/common/enums/payment-intent-event.enum.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/common/enums/payment-intent-event.enum.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/common/enums/payment-intent-event.enum.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/common/enums/payment-intent-event.enum.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">export</span> <span class="kr">enum</span> <span class="nx">PaymentIntentEvent</span> <span class="p">{</span>
<span class="nx">Succeeded</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">payment_intent.succeeded</span><span class="dl">'</span><span class="p">,</span>
<span class="nx">Processing</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">payment_intent.processing</span><span class="dl">'</span><span class="p">,</span>
<span class="nx">Failed</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">payment_intent.payment_failed</span><span class="dl">'</span><span class="p">,</span>
<span class="p">}</span></code></pre></figure>
<h3 id="orders-service">Orders Service</h3>
<p>Here’s our orders service, with some comments added to aid in understanding the new functionality:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/orders.service.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/orders/orders.service.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/orders.service.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/orders.service.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Injectable</span><span class="p">,</span> <span class="nx">UnprocessableEntityException</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CreateOrderDto</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./dto/create-order.dto</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Customer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../customers/entities/customer.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">InjectRepository</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Order</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./entities/order.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Repository</span><span class="p">,</span> <span class="nx">UpdateResult</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ProductsService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../products/products.service</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">StripeService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../stripe/stripe.service</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Stripe</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">stripe</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">PaymentStatus</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../common/enums/payment-status.enum</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">PaymentIntentEvent</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../common/enums/payment-intent-event.enum</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Injectable</span><span class="p">()</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">OrdersService</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span>
<span class="p">@</span><span class="nd">InjectRepository</span><span class="p">(</span><span class="nx">Order</span><span class="p">)</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="nx">ordersRepository</span><span class="p">:</span> <span class="nx">Repository</span><span class="o"><</span><span class="nx">Order</span><span class="o">></span><span class="p">,</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="nx">productsService</span><span class="p">:</span> <span class="nx">ProductsService</span><span class="p">,</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="nx">stripeService</span><span class="p">:</span> <span class="nx">StripeService</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{}</span>
<span class="k">async</span> <span class="nx">create</span><span class="p">(</span>
<span class="nx">createOrderDto</span><span class="p">:</span> <span class="nx">CreateOrderDto</span><span class="p">,</span>
<span class="nx">customer</span><span class="p">:</span> <span class="nx">Customer</span><span class="p">,</span>
<span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Order</span><span class="o">></span> <span class="p">{</span>
<span class="c1">// Check if the products in the order exist</span>
<span class="kd">const</span> <span class="nx">productIds</span> <span class="o">=</span> <span class="nx">createOrderDto</span><span class="p">.</span><span class="nx">orderItems</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">item</span><span class="p">)</span> <span class="o">=></span> <span class="nx">item</span><span class="p">.</span><span class="nx">productId</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">products</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">productsService</span><span class="p">.</span><span class="nx">checkIfProductsExist</span><span class="p">(</span>
<span class="nx">productIds</span><span class="p">,</span>
<span class="p">);</span>
<span class="c1">// If none of the products in the order exist, or only some exist, while others</span>
<span class="c1">// do not, throw an exception</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">products</span> <span class="o">||</span> <span class="nx">products</span><span class="p">.</span><span class="nx">length</span> <span class="o">!=</span> <span class="nx">productIds</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">UnprocessableEntityException</span><span class="p">(</span>
<span class="dl">'</span><span class="s1">The order could not be processed</span><span class="dl">'</span><span class="p">,</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">order</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Order</span><span class="p">({</span>
<span class="na">customerId</span><span class="p">:</span> <span class="nx">customer</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
<span class="na">totalAmount</span><span class="p">:</span> <span class="nx">createOrderDto</span><span class="p">.</span><span class="nx">totalAmount</span><span class="p">,</span>
<span class="p">});</span>
<span class="nx">order</span><span class="p">.</span><span class="nx">orderItems</span> <span class="o">=</span> <span class="nx">createOrderDto</span><span class="p">.</span><span class="nx">orderItems</span><span class="p">;</span>
<span class="c1">// Save the order including its order items as a transaction</span>
<span class="kd">const</span> <span class="nx">savedOrder</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">ordersRepository</span><span class="p">.</span><span class="nx">save</span><span class="p">(</span><span class="nx">order</span><span class="p">);</span>
<span class="c1">// Create a payment intent on Stripe</span>
<span class="kd">const</span> <span class="nx">paymentIntent</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">stripeService</span><span class="p">.</span><span class="nx">createPaymentIntent</span><span class="p">(</span>
<span class="nx">savedOrder</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
<span class="nx">savedOrder</span><span class="p">.</span><span class="nx">totalAmount</span><span class="p">,</span>
<span class="p">);</span>
<span class="kd">const</span> <span class="nx">clientSecret</span> <span class="o">=</span> <span class="nx">paymentIntent</span><span class="p">.</span><span class="nx">client_secret</span><span class="p">;</span>
<span class="c1">// Return the client secret to the client as well as the saved order info</span>
<span class="kd">const</span> <span class="nx">updatedOrder</span> <span class="o">=</span> <span class="p">{</span> <span class="p">...</span><span class="nx">savedOrder</span><span class="p">,</span> <span class="na">clientSecret</span><span class="p">:</span> <span class="nx">clientSecret</span> <span class="p">};</span>
<span class="k">return</span> <span class="nx">updatedOrder</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">async</span> <span class="nx">findOrder</span><span class="p">(</span><span class="nx">id</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Order</span><span class="o">></span> <span class="p">{</span>
<span class="k">return</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">ordersRepository</span><span class="p">.</span><span class="nx">findOneOrFail</span><span class="p">(</span><span class="nx">id</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">async</span> <span class="nx">updateOrder</span><span class="p">(</span><span class="nx">id</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">order</span><span class="p">:</span> <span class="nx">Order</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">UpdateResult</span><span class="o">></span> <span class="p">{</span>
<span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">findOrder</span><span class="p">(</span><span class="nx">id</span><span class="p">);</span>
<span class="k">return</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">ordersRepository</span><span class="p">.</span><span class="nx">update</span><span class="p">(</span><span class="nx">id</span><span class="p">,</span> <span class="nx">order</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">async</span> <span class="nx">updatePaymentStatus</span><span class="p">(</span><span class="nx">event</span><span class="p">:</span> <span class="nx">Stripe</span><span class="p">.</span><span class="nx">Event</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="kr">string</span><span class="o">></span> <span class="p">{</span>
<span class="c1">// Fetch the orderId from the webhook metadata</span>
<span class="kd">const</span> <span class="nx">orderId</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">object</span><span class="p">[</span><span class="dl">'</span><span class="s1">metadata</span><span class="dl">'</span><span class="p">].</span><span class="nx">orderId</span><span class="p">;</span>
<span class="c1">// Lookup the order</span>
<span class="kd">const</span> <span class="nx">order</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">findOrder</span><span class="p">(</span><span class="nx">orderId</span><span class="p">);</span>
<span class="c1">// Check the event type</span>
<span class="k">switch</span> <span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="kd">type</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// If the event type is a succeeded, update the payment status to succeeded</span>
<span class="k">case</span> <span class="nx">PaymentIntentEvent</span><span class="p">.</span><span class="na">Succeeded</span><span class="p">:</span>
<span class="nx">order</span><span class="p">.</span><span class="nx">paymentStatus</span> <span class="o">=</span> <span class="nx">PaymentStatus</span><span class="p">.</span><span class="nx">Succeeded</span><span class="p">;</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="nx">PaymentIntentEvent</span><span class="p">.</span><span class="na">Processing</span><span class="p">:</span>
<span class="c1">// If the event type is processing, update the payment status to processing</span>
<span class="nx">order</span><span class="p">.</span><span class="nx">paymentStatus</span> <span class="o">=</span> <span class="nx">PaymentStatus</span><span class="p">.</span><span class="nx">Processing</span><span class="p">;</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="nx">PaymentIntentEvent</span><span class="p">.</span><span class="na">Failed</span><span class="p">:</span>
<span class="c1">// If the event type is payment_failed, update the payment status to payment_failed</span>
<span class="nx">order</span><span class="p">.</span><span class="nx">paymentStatus</span> <span class="o">=</span> <span class="nx">PaymentStatus</span><span class="p">.</span><span class="nx">Failed</span><span class="p">;</span>
<span class="k">break</span><span class="p">;</span>
<span class="nl">default</span><span class="p">:</span>
<span class="c1">// else, by default the payment status should remain as created</span>
<span class="nx">order</span><span class="p">.</span><span class="nx">paymentStatus</span> <span class="o">=</span> <span class="nx">PaymentStatus</span><span class="p">.</span><span class="nx">Created</span><span class="p">;</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">updateResult</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">updateOrder</span><span class="p">(</span><span class="nx">orderId</span><span class="p">,</span> <span class="nx">order</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">updateResult</span><span class="p">.</span><span class="nx">affected</span> <span class="o">===</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="s2">`Record successfully updated with Payment Status </span><span class="p">${</span><span class="nx">order</span><span class="p">.</span><span class="nx">paymentStatus</span><span class="p">}</span><span class="s2">`</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">UnprocessableEntityException</span><span class="p">(</span>
<span class="dl">'</span><span class="s1">The payment was not successfully updated</span><span class="dl">'</span><span class="p">,</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>The <em>create</em> method assists in creating a new order. Before creating a new order we first start by validating the information contained in the order payload that we receive. We first check if indeed the products in the order exist, and if either none of them exist or just some of them do, we reject the order and throw an exception. Once we have successfully saved the order, we create a stripe payment intent using the stripe service (created in the <a href="/2022/07/stripe-module">previous tutorial</a>). Once the payment intent is successfully created on the Stripe servers, a <strong>client secret</strong> will be returned that is used to complete the payment on stripe. We add this client secret to the response body of the order and return it back to the client.</p>
<p>The <em>updatePaymentStatus</em> method is used by the webhook that we created in the previous tutorial, and is used to set the final payment status of our orders depending on whether the customer’s selected payment status was successful. We by default set all orders that we receive to have a payment status of <strong>Created</strong>. We then update this status depending on what we receive from the stripe webhook.</p>
<h3 id="orders-controller">Orders Controller</h3>
<p>The orders controller has two methods: <em>create</em> and <em>webhook</em> that call the respective service methods in the orders service. We protect the create method using an auth guard since we want only authenticated customers to create orders:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/orders.controller.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/orders/orders.controller.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/orders.controller.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/orders.controller.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Controller</span><span class="p">,</span> <span class="nx">Post</span><span class="p">,</span> <span class="nx">Body</span><span class="p">,</span> <span class="nx">UseGuards</span><span class="p">,</span> <span class="nx">Req</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">OrdersService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./orders.service</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CreateOrderDto</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./dto/create-order.dto</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">JwtAuthGuard</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../auth/guards/jwt-auth.guard</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Request</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">express</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Customer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../customers/entities/customer.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Stripe</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">stripe</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Order</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./entities/order.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Controller</span><span class="p">(</span><span class="dl">'</span><span class="s1">orders</span><span class="dl">'</span><span class="p">)</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">OrdersController</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span><span class="k">private</span> <span class="k">readonly</span> <span class="nx">ordersService</span><span class="p">:</span> <span class="nx">OrdersService</span><span class="p">)</span> <span class="p">{}</span>
<span class="p">@</span><span class="nd">UseGuards</span><span class="p">(</span><span class="nx">JwtAuthGuard</span><span class="p">)</span>
<span class="p">@</span><span class="nd">Post</span><span class="p">()</span>
<span class="nx">create</span><span class="p">(</span>
<span class="p">@</span><span class="nd">Body</span><span class="p">()</span> <span class="nx">createOrderDto</span><span class="p">:</span> <span class="nx">CreateOrderDto</span><span class="p">,</span>
<span class="p">@</span><span class="nd">Req</span><span class="p">()</span> <span class="nx">req</span><span class="p">:</span> <span class="nx">Request</span><span class="p">,</span>
<span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Order</span><span class="o">></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">customer</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">user</span> <span class="k">as</span> <span class="nx">Customer</span><span class="p">;</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">ordersService</span><span class="p">.</span><span class="nx">create</span><span class="p">(</span><span class="nx">createOrderDto</span><span class="p">,</span> <span class="nx">customer</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">@</span><span class="nd">Post</span><span class="p">(</span><span class="dl">'</span><span class="s1">stripe_webhook</span><span class="dl">'</span><span class="p">)</span>
<span class="k">async</span> <span class="nx">webhook</span><span class="p">(@</span><span class="nd">Body</span><span class="p">()</span> <span class="nx">event</span><span class="p">:</span> <span class="nx">Stripe</span><span class="p">.</span><span class="nx">Event</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">object</span><span class="o">></span> <span class="p">{</span>
<span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">ordersService</span><span class="p">.</span><span class="nx">updatePaymentStatus</span><span class="p">(</span><span class="nx">event</span><span class="p">);</span>
<span class="k">return</span> <span class="p">{</span> <span class="na">message</span><span class="p">:</span> <span class="dl">'</span><span class="s1">success</span><span class="dl">'</span> <span class="p">};</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>The <em>webhook</em> method is for updating the order payment status.</p>
<h3 id="unit-tests">Unit Tests</h3>
<p>As is our practice, we shall be including a unit test for both our controller and service, and they’ll look like so:</p>
<p>Controller Spec:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/spec/orders.controller.spec.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/orders/spec/orders.controller.spec.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/spec/orders.controller.spec.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/spec/orders.controller.spec.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Test</span><span class="p">,</span> <span class="nx">TestingModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/testing</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">OrdersController</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../orders.controller</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">OrdersService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../orders.service</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">OrdersController</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">controller</span><span class="p">:</span> <span class="nx">OrdersController</span><span class="p">;</span>
<span class="nx">beforeEach</span><span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="na">module</span><span class="p">:</span> <span class="nx">TestingModule</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">Test</span><span class="p">.</span><span class="nx">createTestingModule</span><span class="p">({</span>
<span class="na">controllers</span><span class="p">:</span> <span class="p">[</span><span class="nx">OrdersController</span><span class="p">],</span>
<span class="na">providers</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="na">provide</span><span class="p">:</span> <span class="nx">OrdersService</span><span class="p">,</span>
<span class="na">useValue</span><span class="p">:</span> <span class="p">{},</span>
<span class="p">},</span>
<span class="p">],</span>
<span class="p">}).</span><span class="nx">compile</span><span class="p">();</span>
<span class="nx">controller</span> <span class="o">=</span> <span class="kr">module</span><span class="p">.</span><span class="kd">get</span><span class="o"><</span><span class="nx">OrdersController</span><span class="o">></span><span class="p">(</span><span class="nx">OrdersController</span><span class="p">);</span>
<span class="p">});</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should be defined</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">expect</span><span class="p">(</span><span class="nx">controller</span><span class="p">).</span><span class="nx">toBeDefined</span><span class="p">();</span>
<span class="p">});</span>
<span class="p">});</span></code></pre></figure>
<p>This only includes a basic test for definition, as most of the functionality will be tested in an e2e spec.</p>
<p>Service Spec:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/spec/orders.service.spec.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/orders/spec/orders.service.spec.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/spec/orders.service.spec.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/spec/orders.service.spec.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Test</span><span class="p">,</span> <span class="nx">TestingModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/testing</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">getRepositoryToken</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">Stripe</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">stripe</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">UpdateResult</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Customer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../customers/entities/customer.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ProductsService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../products/products.service</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">StripeService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../stripe/stripe.service</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CreateOrderDto</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../dto/create-order.dto</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Order</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../entities/order.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">OrdersService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../orders.service</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">OrdersService</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">service</span><span class="p">:</span> <span class="nx">OrdersService</span><span class="p">;</span>
<span class="nx">beforeEach</span><span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">createMock</span> <span class="o">=</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">((</span><span class="na">dto</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">dto</span><span class="p">;</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">saveMock</span> <span class="o">=</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">((</span><span class="na">dto</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">dto</span><span class="p">;</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">findOneMock</span> <span class="o">=</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">((</span><span class="na">dto</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">dto</span><span class="p">;</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">MockRepository</span> <span class="o">=</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">().</span><span class="nx">mockImplementation</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">create</span><span class="p">:</span> <span class="nx">createMock</span><span class="p">,</span>
<span class="na">save</span><span class="p">:</span> <span class="nx">saveMock</span><span class="p">,</span>
<span class="na">findOne</span><span class="p">:</span> <span class="nx">findOneMock</span><span class="p">,</span>
<span class="p">};</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="na">module</span><span class="p">:</span> <span class="nx">TestingModule</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">Test</span><span class="p">.</span><span class="nx">createTestingModule</span><span class="p">({</span>
<span class="na">providers</span><span class="p">:</span> <span class="p">[</span>
<span class="nx">OrdersService</span><span class="p">,</span>
<span class="p">{</span>
<span class="na">provide</span><span class="p">:</span> <span class="nx">getRepositoryToken</span><span class="p">(</span><span class="nx">Order</span><span class="p">),</span>
<span class="na">useClass</span><span class="p">:</span> <span class="nx">MockRepository</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">provide</span><span class="p">:</span> <span class="nx">ProductsService</span><span class="p">,</span>
<span class="na">useValue</span><span class="p">:</span> <span class="p">{},</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">provide</span><span class="p">:</span> <span class="nx">StripeService</span><span class="p">,</span>
<span class="na">useValue</span><span class="p">:</span> <span class="p">{},</span>
<span class="p">},</span>
<span class="p">],</span>
<span class="p">}).</span><span class="nx">compile</span><span class="p">();</span>
<span class="nx">service</span> <span class="o">=</span> <span class="kr">module</span><span class="p">.</span><span class="kd">get</span><span class="o"><</span><span class="nx">OrdersService</span><span class="o">></span><span class="p">(</span><span class="nx">OrdersService</span><span class="p">);</span>
<span class="p">});</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should be defined</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">expect</span><span class="p">(</span><span class="nx">service</span><span class="p">).</span><span class="nx">toBeDefined</span><span class="p">();</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">creating an order</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should return a new order</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">order</span><span class="p">:</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Order</span><span class="o">></span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">testOrderDto</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">CreateOrderDto</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">testCustomer</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Customer</span><span class="p">();</span>
<span class="nx">jest</span><span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">service</span><span class="p">,</span> <span class="dl">'</span><span class="s1">create</span><span class="dl">'</span><span class="p">).</span><span class="nx">mockImplementation</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">order</span><span class="p">);</span>
<span class="nx">expect</span><span class="p">(</span><span class="k">await</span> <span class="nx">service</span><span class="p">.</span><span class="nx">create</span><span class="p">(</span><span class="nx">testOrderDto</span><span class="p">,</span> <span class="nx">testCustomer</span><span class="p">)).</span><span class="nx">toBe</span><span class="p">(</span><span class="nx">order</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">finding an order</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should return one order</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">order</span><span class="p">:</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Order</span><span class="o">></span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">testOrderId</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">2173dd22-42ed-4091-bb46-aaca401efa46</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">jest</span><span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">service</span><span class="p">,</span> <span class="dl">'</span><span class="s1">findOrder</span><span class="dl">'</span><span class="p">).</span><span class="nx">mockImplementation</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">order</span><span class="p">);</span>
<span class="nx">expect</span><span class="p">(</span><span class="k">await</span> <span class="nx">service</span><span class="p">.</span><span class="nx">findOrder</span><span class="p">(</span><span class="nx">testOrderId</span><span class="p">)).</span><span class="nx">toBe</span><span class="p">(</span><span class="nx">order</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">updating an order</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should return an update result</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">updateResult</span><span class="p">:</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">UpdateResult</span><span class="o">></span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">testOrderId</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">2173dd22-42ed-4091-bb46-aaca401efa46</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">testOrder</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Order</span><span class="p">();</span>
<span class="nx">jest</span><span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">service</span><span class="p">,</span> <span class="dl">'</span><span class="s1">updateOrder</span><span class="dl">'</span><span class="p">).</span><span class="nx">mockImplementation</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">updateResult</span><span class="p">);</span>
<span class="nx">expect</span><span class="p">(</span><span class="k">await</span> <span class="nx">service</span><span class="p">.</span><span class="nx">updateOrder</span><span class="p">(</span><span class="nx">testOrderId</span><span class="p">,</span> <span class="nx">testOrder</span><span class="p">)).</span><span class="nx">toBe</span><span class="p">(</span>
<span class="nx">updateResult</span><span class="p">,</span>
<span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">updating an order payment status</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should return a string</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">updateResult</span><span class="p">:</span> <span class="nb">Promise</span><span class="o"><</span><span class="kr">string</span><span class="o">></span><span class="p">;</span>
<span class="kd">let</span> <span class="na">event</span><span class="p">:</span> <span class="nx">Stripe</span><span class="p">.</span><span class="nx">Event</span><span class="p">;</span>
<span class="nx">jest</span>
<span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">service</span><span class="p">,</span> <span class="dl">'</span><span class="s1">updatePaymentStatus</span><span class="dl">'</span><span class="p">)</span>
<span class="p">.</span><span class="nx">mockImplementation</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">updateResult</span><span class="p">);</span>
<span class="nx">expect</span><span class="p">(</span><span class="k">await</span> <span class="nx">service</span><span class="p">.</span><span class="nx">updatePaymentStatus</span><span class="p">(</span><span class="nx">event</span><span class="p">)).</span><span class="nx">toBe</span><span class="p">(</span><span class="nx">updateResult</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p">});</span></code></pre></figure>
<p>As before, we heavily use mocks to test the methods in our orders service.</p>
<p>Our tests should turn green when run:</p>
<p><img src="/assets/storefront/37-orders-controller-spec.png" alt="Orders Controller Spec" /></p>
<p><img src="/assets/storefront/38-orders-service-spec.png" alt="Orders Service Spec" /></p>
<h3 id="postman-tests">Postman Tests</h3>
<p>We’ll use postman as usual for our manual tests. Here’s our order creation endpoint:</p>
<p><img src="/assets/storefront/39-create-order.png" alt="Create Order" /></p>
<p>We’re creating an order with a total value of <strong>$55.25</strong>.</p>
<p>Don’t forget to run the stripe CLI in a separate terminal tab.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>stripe listen <span class="nt">--forward-to</span> localhost:3000/orders/stripe_webhook
</code></pre></div></div>
<p>Once an order is created, you should see some output printed by the stripe CLI:</p>
<p><img src="/assets/storefront/40-stripe-cli-output.png" alt="Stripe CLI output" /></p>
<p>The payment intent has been created on stripe’s servers, and in turn we are informed of the same via a <strong>payment_intent.created</strong> event.</p>
<p>We can confirm the same when we login to our stripe dashboard:</p>
<p><img src="/assets/storefront/41-payment-intent-created.png" alt="Stripe Dashboard Payment Created" /></p>
<p>Our payment has a status of <strong>Incomplete</strong> on the stripe dashboard, since according to our chosen payment flow, we’ll require frontend UI to prompt the user to select a valid payment method (credit/debit card, klarna or ali pay), then submit all the relevant details to complete the payment. We’ll cover this in a future tutorial.</p>Isaiah MucheneToday, we shall be working on the orders module. This module ties in logic from all other modules, and is where the bulk of the order processing will be done. In case of any issues, you can refer to my Github repositoryCreate a Stripe Module in Nest.js2022-07-29T17:30:00+03:002022-07-29T17:30:00+03:00/2022/07/stripe-module<div class="card seriesNote">
<p class="text-center">
This article is <strong>Part 8</strong> in a <strong>10 Part</strong> Series.
</p>
<ul class="list-group list-group-flush">
<li class="list-group-item"> Part 1 -
<a href="/2022/04/bootstrapping-nestjs-ubuntu">Bootstrapping a Nest.js Project on Ubuntu</a>
</li>
<li class="list-group-item"> Part 2 -
<a href="/2022/04/create-nestjs-migrations">Create Nest.js Migrations</a>
</li>
<li class="list-group-item"> Part 3 -
<a href="/2022/04/create-nestjs-crud-resources">Create Nest.js CRUD Resources</a>
</li>
<li class="list-group-item"> Part 4 -
<a href="/2022/06/create-nestjs-seeders">Create Nest.js Seeders</a>
</li>
<li class="list-group-item"> Part 5 -
<a href="/2022/07/customer-module">Create a Customer Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 6 -
<a href="/2022/07/auth-module">Nest.js Authentication</a>
</li>
<li class="list-group-item"> Part 7 -
<a href="/2022/07/products-module">Create a Products Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 8 -
This Article
</li>
<li class="list-group-item"> Part 9 -
<a href="/2022/08/orders-module">Create an Orders Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 10 -
<a href="/2022/08/end-to-end-tests">End to End Tests for our Storefront Backend</a>
</li>
</ul>
</div>
<p><br /></p>
<p>Next, we are moving on to adding a payment integration to our backend. In this case, we shall be using <a href="https://stripe.com/">Stripe</a>, which is a well known payments processor for C2B (Customer to Business) transactions. First you’ll have to create a stripe account. Once you do so, take note of the <strong>Secret key</strong> that will be shown to the right of the dashboard. In case of any issues, you can refer to my Github repository <a href="https://github.com/imuchene/storefront-backend">here</a>.</p>
<h3 id="add-payment_status-field-to-orders">Add payment_status field to orders</h3>
<p>We’ll make a small change to our orders table by adding a <strong>payment_status</strong> field to indicate whether payment for an order is processing, sucessful or unsuccessful. All orders on being newly created will have a default payment status of <em>Created</em>.</p>
<p>Within the project root, run:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm run migrations:create AddPaymentStatusToOrders
</code></pre></div></div>
<p>Here’s how the migration should look like:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/migrations/1652272019740-AddPaymentStatusToOrders.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/migrations/1652272019740-AddPaymentStatusToOrders.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/migrations/1652272019740-AddPaymentStatusToOrders.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/migrations/1652272019740-AddPaymentStatusToOrders.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">MigrationInterface</span><span class="p">,</span> <span class="nx">QueryRunner</span><span class="p">,</span> <span class="nx">TableColumn</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">AddPaymentStatusToOrders1652272019740</span>
<span class="k">implements</span> <span class="nx">MigrationInterface</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">async</span> <span class="nx">up</span><span class="p">(</span><span class="nx">queryRunner</span><span class="p">:</span> <span class="nx">QueryRunner</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="k">void</span><span class="o">></span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">queryRunner</span><span class="p">.</span><span class="nx">addColumn</span><span class="p">(</span>
<span class="dl">'</span><span class="s1">orders</span><span class="dl">'</span><span class="p">,</span>
<span class="k">new</span> <span class="nx">TableColumn</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">payment_status</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">enum</span><span class="dl">'</span><span class="p">,</span>
<span class="na">enum</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">Created</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">Processing</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">Succeeded</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">Failed</span><span class="dl">'</span><span class="p">],</span>
<span class="na">default</span><span class="p">:</span> <span class="s2">`'Created'`</span><span class="p">,</span>
<span class="p">}),</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">async</span> <span class="nx">down</span><span class="p">(</span><span class="nx">queryRunner</span><span class="p">:</span> <span class="nx">QueryRunner</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="k">void</span><span class="o">></span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">queryRunner</span><span class="p">.</span><span class="nx">dropColumn</span><span class="p">(</span><span class="dl">'</span><span class="s1">orders</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">payment_status</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<h3 id="enable-klarna-and-alipay-on-the-stripe-dashboard">Enable klarna and alipay on the stripe dashboard</h3>
<p>In addition to accepting credit/debit card transactions, which is enabled by default, we shall be also supporting the above two payment methods in our backend. In order to allow the above two, head over to Product settings on the stripe dashboard, then click the Payment methods link. Under <strong>Wallets</strong> make sure Alipay is turned on:</p>
<p><img src="/assets/storefront/34-ali-pay.png" alt="Alipay" /></p>
<p>Under <strong>Buy now, pay later</strong> make sure Klarna is turned on:</p>
<p><img src="/assets/storefront/35-klarna.png" alt="Klarna" /></p>
<h3 id="install-the-stripe-npm-module">Install the stripe npm module</h3>
<p>Still under the project root, run:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i <span class="nt">--save</span> stripe
</code></pre></div></div>
<h3 id="add-stripe-credentials-from-the-env">Add stripe credentials from the .env</h3>
<p>Update your .env file with your stripe secret key, default currency and frontend URL. Later on, we shall build a frontend in Angular to utilise the stripe functionality:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Stripe Credentials</span>
<span class="nv">STRIPE_SECRET_KEY</span><span class="o">=</span>your_stripe_secret_key
<span class="nv">STRIPE_CURRENCY</span><span class="o">=</span>usd
<span class="nv">FRONTEND_URL</span><span class="o">=</span>http://localhost:4200
</code></pre></div></div>
<h3 id="generate-a-stripe-nestjs-module">Generate a stripe nest.js module</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nest g module modules/stripe
</code></pre></div></div>
<h3 id="generate-a-stripe-nestjs-service">Generate a stripe nest.js service</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nest g s modules/stripe
</code></pre></div></div>
<h3 id="install-the-stripe-cli-command-line-interface-for-linux">Install the stripe CLI (Command Line Interface) for Linux</h3>
<p>The stripe CLI is very useful during development to test some functionalities such as <a href="https://stripe.com/docs/webhooks">webhooks</a>, without the need of having our backend API deployed to the Internet. We shall be following the instructions contained <a href="https://stripe.com/docs/stripe-cli#install">here</a>, with a few modifications to install the CLI. On <a href="https://stripe.com/docs/stripe-cli">this</a> page there are corresponding instructions for MacOS and Windows.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> /tmp/
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl https://packages.stripe.dev/api/security/keypair/stripe-cli-gpg/public | gpg <span class="nt">--dearmor</span> <span class="o">></span> stripe.gpg
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo install</span> <span class="nt">-o</span> root <span class="nt">-g</span> root <span class="nt">-m</span> 644 stripe.gpg /etc/apt/trusted.gpg.d/
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s2">"deb https://packages.stripe.dev/stripe-cli-debian-local stable main"</span> | <span class="nb">sudo tee</span> <span class="nt">-a</span> /etc/apt/sources.list.d/stripe.list
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt update
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt <span class="nb">install </span>stripe
</code></pre></div></div>
<h3 id="move-back-to-the-previous-directory">Move back to the previous directory</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> -
</code></pre></div></div>
<h3 id="login-to-stripe">Login to stripe</h3>
<p>Once the CLI is installed, a new console tab, run the following commands. The API key you should enter is the <strong>Secret Key</strong> mentioned above.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>stripe login <span class="nt">--interactive</span>
</code></pre></div></div>
<p>Output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Enter your API key:
Your API key is: sk_test_<span class="k">***********************************************************************************************</span>WwYT
How would you like to identify this device <span class="k">in </span>the Stripe Dashboard? <span class="o">[</span>default: pop-os]
<span class="o">></span> Done! The Stripe CLI is configured <span class="k">for </span>your account with account <span class="nb">id </span>acct_1JNFj6GIyORaO7x3
</code></pre></div></div>
<h3 id="forward-events-to-the-api-webhook-url">Forward events to the API webhook URL</h3>
<p>A <a href="https://en.wikipedia.org/wiki/Webhook">webhook</a> is an HTTP request (usually containing JSON data) that is sent from one backend application to another when a certain event occurs on the sending application. The receiving application has to anticipate this request in advance and have a means of receiving the data from the request in the form af an HTTP endpoint. In our case, the sending application is stripe, while our backend is the receiving application. The event that triggers the webhook is when a new payment is received by stripe from our backend application. In the next tutorial, we shall cover the webhook in more details. For now, let us set our endpoint to the URL: <em>localhost:3000/orders/stripe_webhook</em></p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>stripe listen <span class="nt">--forward-to</span> localhost:3000/orders/stripe_webhook
</code></pre></div></div>
<p>The above command notifies stripe that instead of forwarding webhooks to a public URL, to instead send the same to our locally running application from where we can act on our data.</p>
<p>Output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> Ready! You are using Stripe API Version <span class="o">[</span>2020-08-27]. Your webhook signing secret is whsec_a7e5d8effe3f7d8439a6a1f9a74ed95c807c451e8e063dde64e295d96a50407a <span class="o">(</span>^C to quit<span class="o">)</span>
</code></pre></div></div>
<h3 id="stripe-module">Stripe Module</h3>
<p>Here’s how the stripe module file should look like:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/stripe/stripe.module.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/stripe/stripe.module.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/stripe/stripe.module.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/stripe/stripe.module.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Module</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">StripeService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./stripe.service</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Module</span><span class="p">({</span>
<span class="na">providers</span><span class="p">:</span> <span class="p">[</span><span class="nx">StripeService</span><span class="p">],</span>
<span class="na">exports</span><span class="p">:</span> <span class="p">[</span><span class="nx">StripeService</span><span class="p">],</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">StripeModule</span> <span class="p">{}</span></code></pre></figure>
<h3 id="stripe-service">Stripe Service</h3>
<p>Our main payment logic is contained within the service, and here’s the stripe service file:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/stripe/stripe.service.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/stripe/stripe.service.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/stripe/stripe.service.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/stripe/stripe.service.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span>
<span class="nx">Injectable</span><span class="p">,</span>
<span class="nx">Logger</span><span class="p">,</span>
<span class="nx">UnprocessableEntityException</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ConfigService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/config</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Stripe</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">stripe</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">util</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">util</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Injectable</span><span class="p">()</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">StripeService</span> <span class="p">{</span>
<span class="k">readonly</span> <span class="nx">stripe</span><span class="p">:</span> <span class="nx">Stripe</span><span class="p">;</span>
<span class="kd">constructor</span><span class="p">(</span><span class="k">readonly</span> <span class="nx">configService</span><span class="p">:</span> <span class="nx">ConfigService</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">stripe</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Stripe</span><span class="p">(</span><span class="nx">configService</span><span class="p">.</span><span class="kd">get</span><span class="o"><</span><span class="kr">string</span><span class="o">></span><span class="p">(</span><span class="dl">'</span><span class="s1">STRIPE_SECRET_KEY</span><span class="dl">'</span><span class="p">),</span> <span class="p">{</span>
<span class="na">apiVersion</span><span class="p">:</span> <span class="dl">'</span><span class="s1">2020-08-27</span><span class="dl">'</span><span class="p">,</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="k">async</span> <span class="nx">createPaymentIntent</span><span class="p">(</span>
<span class="nx">orderId</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="nx">totalAmount</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span>
<span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Stripe</span><span class="p">.</span><span class="nx">PaymentIntent</span><span class="o">></span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">orderId</span> <span class="o">||</span> <span class="nx">totalAmount</span> <span class="o"><</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">UnprocessableEntityException</span><span class="p">(</span>
<span class="dl">'</span><span class="s1">The payment intent could not be created</span><span class="dl">'</span><span class="p">,</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="k">try</span> <span class="p">{</span>
<span class="kd">const</span> <span class="na">paymentIntentParams</span><span class="p">:</span> <span class="nx">Stripe</span><span class="p">.</span><span class="nx">PaymentIntentCreateParams</span> <span class="o">=</span> <span class="p">{</span>
<span class="c1">// Total amount to be sent is converted to cents to be used by the Stripe API</span>
<span class="na">amount</span><span class="p">:</span> <span class="nb">Number</span><span class="p">(</span><span class="nx">totalAmount</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span><span class="p">,</span>
<span class="na">currency</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">configService</span><span class="p">.</span><span class="kd">get</span><span class="o"><</span><span class="kr">string</span><span class="o">></span><span class="p">(</span><span class="dl">'</span><span class="s1">STRIPE_CURRENCY</span><span class="dl">'</span><span class="p">),</span>
<span class="na">payment_method_types</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">card</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">klarna</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">alipay</span><span class="dl">'</span><span class="p">],</span>
<span class="na">metadata</span><span class="p">:</span> <span class="p">{</span> <span class="na">orderId</span><span class="p">:</span> <span class="nx">orderId</span> <span class="p">},</span>
<span class="p">};</span>
<span class="k">return</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">stripe</span><span class="p">.</span><span class="nx">paymentIntents</span><span class="p">.</span><span class="nx">create</span><span class="p">(</span><span class="nx">paymentIntentParams</span><span class="p">);</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">Logger</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span>
<span class="dl">'</span><span class="s1">[stripeService] Error creating a payment intent</span><span class="dl">'</span><span class="p">,</span>
<span class="nx">util</span><span class="p">.</span><span class="nx">inspect</span><span class="p">(</span><span class="nx">error</span><span class="p">),</span>
<span class="p">);</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">UnprocessableEntityException</span><span class="p">(</span>
<span class="dl">'</span><span class="s1">The payment intent could not be created</span><span class="dl">'</span><span class="p">,</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>We will be using the <a href="https://stripe.com/docs/payments/accept-a-payment?platform=web&ui=elements">custom payments flow</a> to accept payments within our application. To do so, we need to first create a <a href="https://stripe.com/docs/api/payment_intents">payment intent</a> object on our backend (shown in the <em>createPaymentIntent</em> method above). This payment intent symbolises the customer’s intention to pay for a product. In order to complete or finalise the payment intent, it will have to be confirmed on the frontend side using the stripe javascript API. We will cover this bit in more depth in a later tutorial.</p>
<p>Before we build the frontend, we shall first integrate and test the above functionality with the orders module, which we’ll create in the next tutorial. You’ll notice that when creating a payment intent on stripe, all dollar amounts must first be converted to cents (hence multiplication by 100), then we should not forget to allow <strong>klarna</strong> and <strong>alipay</strong> as payment method types. The metadata field is for our own internal use, as later on we will need it to do order processing once we receive the webhook from stripe.</p>
<h3 id="unit-tests">Unit Tests</h3>
<p>As is our practice, we shall be including a unit test for our service, and it’ll look like so:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/stripe/spec/stripe.service.spec.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/stripe/spec/stripe.service.spec.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/stripe/spec/stripe.service.spec.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/stripe/spec/stripe.service.spec.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">UnprocessableEntityException</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ConfigService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/config</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Test</span><span class="p">,</span> <span class="nx">TestingModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/testing</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Stripe</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">stripe</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">StripeService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../stripe.service</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">StripeService</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">service</span><span class="p">:</span> <span class="nx">StripeService</span><span class="p">;</span>
<span class="nx">beforeEach</span><span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">mockGetFunction</span> <span class="o">=</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">((</span><span class="na">data</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">data</span><span class="p">;</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">mockConfigService</span> <span class="o">=</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">().</span><span class="nx">mockImplementation</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">get</span><span class="p">:</span> <span class="nx">mockGetFunction</span><span class="p">,</span>
<span class="p">};</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="na">module</span><span class="p">:</span> <span class="nx">TestingModule</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">Test</span><span class="p">.</span><span class="nx">createTestingModule</span><span class="p">({</span>
<span class="na">providers</span><span class="p">:</span> <span class="p">[</span>
<span class="nx">StripeService</span><span class="p">,</span>
<span class="p">{</span>
<span class="na">provide</span><span class="p">:</span> <span class="nx">ConfigService</span><span class="p">,</span>
<span class="na">useClass</span><span class="p">:</span> <span class="nx">mockConfigService</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">],</span>
<span class="p">}).</span><span class="nx">compile</span><span class="p">();</span>
<span class="nx">service</span> <span class="o">=</span> <span class="kr">module</span><span class="p">.</span><span class="kd">get</span><span class="o"><</span><span class="nx">StripeService</span><span class="o">></span><span class="p">(</span><span class="nx">StripeService</span><span class="p">);</span>
<span class="p">});</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should be defined</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">expect</span><span class="p">(</span><span class="nx">service</span><span class="p">).</span><span class="nx">toBeDefined</span><span class="p">();</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">creating a payment intent</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">throws an error when orderId and totalAmount are not provided</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">expect</span><span class="p">.</span><span class="nx">assertions</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span>
<span class="k">try</span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">service</span><span class="p">.</span><span class="nx">createPaymentIntent</span><span class="p">(</span><span class="dl">''</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">expect</span><span class="p">(</span><span class="nx">error</span><span class="p">).</span><span class="nx">toBeInstanceOf</span><span class="p">(</span><span class="nx">UnprocessableEntityException</span><span class="p">);</span>
<span class="nx">expect</span><span class="p">(</span><span class="nx">error</span><span class="p">.</span><span class="nx">message</span><span class="p">).</span><span class="nx">toBe</span><span class="p">(</span><span class="dl">'</span><span class="s1">The payment intent could not be created</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should return a new payment intent</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">testPaymentIntent</span><span class="p">:</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Stripe</span><span class="p">.</span><span class="nx">PaymentIntent</span><span class="o">></span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">testOrderId</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">21b2090a-d74c-47de-add8-af60d4903123</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">testTotalAmount</span> <span class="o">=</span> <span class="mi">50</span><span class="p">;</span>
<span class="nx">jest</span>
<span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">service</span><span class="p">,</span> <span class="dl">'</span><span class="s1">createPaymentIntent</span><span class="dl">'</span><span class="p">)</span>
<span class="p">.</span><span class="nx">mockImplementation</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">testPaymentIntent</span><span class="p">);</span>
<span class="nx">expect</span><span class="p">(</span>
<span class="k">await</span> <span class="nx">service</span><span class="p">.</span><span class="nx">createPaymentIntent</span><span class="p">(</span><span class="nx">testOrderId</span><span class="p">,</span> <span class="nx">testTotalAmount</span><span class="p">),</span>
<span class="p">).</span><span class="nx">toBe</span><span class="p">(</span><span class="nx">testPaymentIntent</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p">});</span></code></pre></figure>
<p>Our tests should turn green when run:</p>
<p><img src="/assets/storefront/36-stripe-spec.png" alt="Stripe Spec" /></p>Isaiah MucheneNext, we are moving on to adding a payment integration to our backend. In this case, we shall be using Stripe, which is a well known payments processor for C2B (Customer to Business) transactions. First you'll have to create a stripe account.Create a Products Module in Nest.js2022-07-27T17:00:00+03:002022-07-27T17:00:00+03:00/2022/07/products-module<div class="card seriesNote">
<p class="text-center">
This article is <strong>Part 7</strong> in a <strong>10 Part</strong> Series.
</p>
<ul class="list-group list-group-flush">
<li class="list-group-item"> Part 1 -
<a href="/2022/04/bootstrapping-nestjs-ubuntu">Bootstrapping a Nest.js Project on Ubuntu</a>
</li>
<li class="list-group-item"> Part 2 -
<a href="/2022/04/create-nestjs-migrations">Create Nest.js Migrations</a>
</li>
<li class="list-group-item"> Part 3 -
<a href="/2022/04/create-nestjs-crud-resources">Create Nest.js CRUD Resources</a>
</li>
<li class="list-group-item"> Part 4 -
<a href="/2022/06/create-nestjs-seeders">Create Nest.js Seeders</a>
</li>
<li class="list-group-item"> Part 5 -
<a href="/2022/07/customer-module">Create a Customer Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 6 -
<a href="/2022/07/auth-module">Nest.js Authentication</a>
</li>
<li class="list-group-item"> Part 7 -
This Article
</li>
<li class="list-group-item"> Part 8 -
<a href="/2022/07/stripe-module">Create a Stripe Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 9 -
<a href="/2022/08/orders-module">Create an Orders Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 10 -
<a href="/2022/08/end-to-end-tests">End to End Tests for our Storefront Backend</a>
</li>
</ul>
</div>
<p><br /></p>
<p>Today we shall be creating the products module for our backend application, starting as usual with the products module. In case of any issues, you can refer to my Github repository <a href="https://github.com/imuchene/storefront-backend">here</a>.</p>
<h3 id="products-module">Products Module</h3>
<p>Here’s the products module file:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/products/products.module.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/products/products.module.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/products/products.module.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/products/products.module.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Module</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ProductsService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./products.service</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ProductsController</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./products.controller</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">TypeOrmModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Product</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./entities/product.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Module</span><span class="p">({</span>
<span class="na">imports</span><span class="p">:</span> <span class="p">[</span><span class="nx">TypeOrmModule</span><span class="p">.</span><span class="nx">forFeature</span><span class="p">([</span><span class="nx">Product</span><span class="p">])],</span>
<span class="na">controllers</span><span class="p">:</span> <span class="p">[</span><span class="nx">ProductsController</span><span class="p">],</span>
<span class="na">providers</span><span class="p">:</span> <span class="p">[</span><span class="nx">ProductsService</span><span class="p">],</span>
<span class="na">exports</span><span class="p">:</span> <span class="p">[</span><span class="nx">ProductsService</span><span class="p">],</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">ProductsModule</span> <span class="p">{}</span></code></pre></figure>
<h3 id="products-service">Products Service</h3>
<p>Our main product logic is contained within the service, and here’s the products service file:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/products/products.service.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/products/products.service.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/products/products.service.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/products/products.service.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Injectable</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">InjectRepository</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">DeleteResult</span><span class="p">,</span> <span class="nx">Repository</span><span class="p">,</span> <span class="nx">UpdateResult</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CreateProductDto</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./dto/create-product.dto</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">UpdateProductDto</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./dto/update-product.dto</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Product</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./entities/product.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Injectable</span><span class="p">()</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">ProductsService</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span>
<span class="p">@</span><span class="nd">InjectRepository</span><span class="p">(</span><span class="nx">Product</span><span class="p">)</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="nx">productsRepository</span><span class="p">:</span> <span class="nx">Repository</span><span class="o"><</span><span class="nx">Product</span><span class="o">></span><span class="p">,</span>
<span class="p">)</span> <span class="p">{}</span>
<span class="k">async</span> <span class="nx">create</span><span class="p">(</span><span class="nx">createProductDto</span><span class="p">:</span> <span class="nx">CreateProductDto</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Product</span><span class="o">></span> <span class="p">{</span>
<span class="k">return</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">productsRepository</span><span class="p">.</span><span class="nx">save</span><span class="p">(</span><span class="nx">createProductDto</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">async</span> <span class="nx">findAll</span><span class="p">():</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Product</span><span class="p">[]</span><span class="o">></span> <span class="p">{</span>
<span class="k">return</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">productsRepository</span><span class="p">.</span><span class="nx">find</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">async</span> <span class="nx">findOne</span><span class="p">(</span><span class="nx">id</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Product</span><span class="o">></span> <span class="p">{</span>
<span class="k">return</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">productsRepository</span><span class="p">.</span><span class="nx">findOne</span><span class="p">(</span><span class="nx">id</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">async</span> <span class="nx">update</span><span class="p">(</span>
<span class="nx">id</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="nx">updateProductDto</span><span class="p">:</span> <span class="nx">UpdateProductDto</span><span class="p">,</span>
<span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">UpdateResult</span><span class="o">></span> <span class="p">{</span>
<span class="k">return</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">productsRepository</span><span class="p">.</span><span class="nx">update</span><span class="p">(</span><span class="nx">id</span><span class="p">,</span> <span class="nx">updateProductDto</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">async</span> <span class="nx">remove</span><span class="p">(</span><span class="nx">id</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">DeleteResult</span><span class="o">></span> <span class="p">{</span>
<span class="k">return</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">productsRepository</span><span class="p">.</span><span class="nx">softDelete</span><span class="p">(</span><span class="nx">id</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">async</span> <span class="nx">checkIfProductsExist</span><span class="p">(</span><span class="nx">productIds</span><span class="p">:</span> <span class="kr">string</span><span class="p">[]):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Product</span><span class="p">[]</span><span class="o">></span> <span class="p">{</span>
<span class="k">return</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">productsRepository</span><span class="p">.</span><span class="nx">findByIds</span><span class="p">(</span><span class="nx">productIds</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>What’s contained in the service is mainly <a href="https://www.tutoraspire.com/crud-operations-in-sql/">CRUD</a> logic, and the method names pretty much describe the actions that they perform. The <em>create</em> method creates a new product, <em>findAll</em> fetches all products from Postgres, <em>findOne</em> retrieves a single product using its ID, <em>update</em> updates a product, <em>remove</em> however, does something unique. Rather than deleting the product from the database, it performs what’s known as a <a href="https://www.brentozar.com/archive/2020/02/what-are-soft-deletes-and-how-are-they-implemented/">soft delete</a> - the product is retained in our database, but is “hidden” from view. This is performed by adding a <strong>deleted_at</strong> column to the products table. When the softDelete TypeORM method is called within the <em>remove</em> method, the deleted_at column is automatically populated with a timestamp. Later on, when <em>findAll</em> or <em>findOne</em> methods are used to fetch data, the affected record is automatically omitted by TypeORM in the result set.</p>
<p>Lastly, the <em>checkIfProductsExist</em> method returns an array of products based off their corresponding IDs, which are also supplied as an array. It will be used later in the orders service.</p>
<h3 id="product-entity">Product Entity</h3>
<p>The products entity file has remained unchanged from the one in previous tutorials:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/products/entities/product.entity.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/products/entities/product.entity.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/products/entities/product.entity.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/products/entities/product.entity.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">OrderItem</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../orders/entities/order-item.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span>
<span class="nx">Column</span><span class="p">,</span>
<span class="nx">CreateDateColumn</span><span class="p">,</span>
<span class="nx">DeleteDateColumn</span><span class="p">,</span>
<span class="nx">Entity</span><span class="p">,</span>
<span class="nx">OneToMany</span><span class="p">,</span>
<span class="nx">PrimaryGeneratedColumn</span><span class="p">,</span>
<span class="nx">UpdateDateColumn</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Entity</span><span class="p">(</span><span class="dl">'</span><span class="s1">products</span><span class="dl">'</span><span class="p">)</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">Product</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span><span class="nx">intialData</span><span class="p">:</span> <span class="nb">Partial</span><span class="o"><</span><span class="nx">Product</span><span class="o">></span> <span class="o">=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">intialData</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="nb">Object</span><span class="p">.</span><span class="nx">assign</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="nx">intialData</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">@</span><span class="nd">PrimaryGeneratedColumn</span><span class="p">(</span><span class="dl">'</span><span class="s1">uuid</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">id</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Column</span><span class="p">({</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">varchar</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Column</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">unit_price</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">numeric</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">unitPrice</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Column</span><span class="p">({</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">text</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">description</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Column</span><span class="p">({</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">varchar</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">image</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">CreateDateColumn</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">created_at</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">timestamptz</span><span class="dl">'</span><span class="p">,</span>
<span class="na">default</span><span class="p">:</span> <span class="dl">'</span><span class="s1">now()</span><span class="dl">'</span><span class="p">,</span>
<span class="na">readonly</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="p">})</span>
<span class="nx">createdAt</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">UpdateDateColumn</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">updated_at</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">timestamptz</span><span class="dl">'</span><span class="p">,</span>
<span class="na">default</span><span class="p">:</span> <span class="dl">'</span><span class="s1">now()</span><span class="dl">'</span><span class="p">,</span>
<span class="p">})</span>
<span class="nx">updatedAt</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">DeleteDateColumn</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">deleted_at</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">timestamptz</span><span class="dl">'</span><span class="p">,</span>
<span class="p">})</span>
<span class="nx">deletedAt</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">OneToMany</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">OrderItem</span><span class="p">,</span> <span class="p">(</span><span class="nx">orderItem</span><span class="p">)</span> <span class="o">=></span> <span class="nx">orderItem</span><span class="p">.</span><span class="nx">product</span><span class="p">)</span>
<span class="nx">orderItems</span><span class="p">:</span> <span class="nx">OrderItem</span><span class="p">[];</span>
<span class="p">}</span></code></pre></figure>
<h3 id="product-dtos">Product DTOs</h3>
<p>Here are the product DTO files.</p>
<p>Create Product DTO:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/products/dto/create-product.dto.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/products/dto/create-product.dto.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/products/dto/create-product.dto.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/products/dto/create-product.dto.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">PartialType</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/mapped-types</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">IsNotEmpty</span><span class="p">,</span> <span class="nx">IsNumber</span><span class="p">,</span> <span class="nx">IsString</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">class-validator</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Product</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../entities/product.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">CreateProductDto</span> <span class="kd">extends</span> <span class="nx">PartialType</span><span class="p">(</span><span class="nx">Product</span><span class="p">)</span> <span class="p">{</span>
<span class="p">@</span><span class="nd">IsString</span><span class="p">()</span>
<span class="p">@</span><span class="nd">IsNotEmpty</span><span class="p">()</span>
<span class="nx">name</span><span class="p">?:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">IsNumber</span><span class="p">()</span>
<span class="p">@</span><span class="nd">IsNotEmpty</span><span class="p">()</span>
<span class="nx">unitPrice</span><span class="p">?:</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">@</span><span class="nd">IsString</span><span class="p">()</span>
<span class="p">@</span><span class="nd">IsNotEmpty</span><span class="p">()</span>
<span class="nx">description</span><span class="p">?:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>Update Product DTO:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/products/dto/update-product.dto.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/products/dto/update-product.dto.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/products/dto/update-product.dto.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/products/dto/update-product.dto.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">PartialType</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/mapped-types</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CreateProductDto</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./create-product.dto</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">UpdateProductDto</span> <span class="kd">extends</span> <span class="nx">PartialType</span><span class="p">(</span><span class="nx">CreateProductDto</span><span class="p">)</span> <span class="p">{}</span></code></pre></figure>
<p>When creating a new product, we will be validating that both the product name and descriptions exist and are valid strings. We also validate that the product’s unit price exists and is a valid number.</p>
<h3 id="product-controller">Product Controller</h3>
<p>Here is the products controller. We are using the <strong>JwtAuthGuard</strong> that we created in the <a href="/2022/07/auth-module">previous tutorial</a> to limit creating, updating and deleting products to authenticated customers. This is not the best solution, as ideally those functions should be performed by Administrators only, not regular customers. In future, we shall further enhance our backend via RBAC (Role Based Access Control).</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/products/products.controller.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/products/products.controller.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/products/products.controller.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/products/products.controller.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span>
<span class="nx">Controller</span><span class="p">,</span>
<span class="nx">Get</span><span class="p">,</span>
<span class="nx">Post</span><span class="p">,</span>
<span class="nx">Body</span><span class="p">,</span>
<span class="nx">Patch</span><span class="p">,</span>
<span class="nx">Param</span><span class="p">,</span>
<span class="nx">Delete</span><span class="p">,</span>
<span class="nx">UseGuards</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ProductsService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./products.service</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CreateProductDto</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./dto/create-product.dto</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">UpdateProductDto</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./dto/update-product.dto</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">JwtAuthGuard</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../auth/guards/jwt-auth.guard</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Product</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./entities/product.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">DeleteResult</span><span class="p">,</span> <span class="nx">UpdateResult</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Controller</span><span class="p">(</span><span class="dl">'</span><span class="s1">products</span><span class="dl">'</span><span class="p">)</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">ProductsController</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span><span class="k">private</span> <span class="k">readonly</span> <span class="nx">productsService</span><span class="p">:</span> <span class="nx">ProductsService</span><span class="p">)</span> <span class="p">{}</span>
<span class="p">@</span><span class="nd">UseGuards</span><span class="p">(</span><span class="nx">JwtAuthGuard</span><span class="p">)</span>
<span class="p">@</span><span class="nd">Post</span><span class="p">()</span>
<span class="nx">create</span><span class="p">(@</span><span class="nd">Body</span><span class="p">()</span> <span class="nx">createProductDto</span><span class="p">:</span> <span class="nx">CreateProductDto</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Product</span><span class="o">></span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">productsService</span><span class="p">.</span><span class="nx">create</span><span class="p">(</span><span class="nx">createProductDto</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">@</span><span class="nd">Get</span><span class="p">()</span>
<span class="nx">findAll</span><span class="p">():</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Product</span><span class="p">[]</span><span class="o">></span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">productsService</span><span class="p">.</span><span class="nx">findAll</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">@</span><span class="nd">Get</span><span class="p">(</span><span class="dl">'</span><span class="s1">:id</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">findOne</span><span class="p">(@</span><span class="nd">Param</span><span class="p">(</span><span class="dl">'</span><span class="s1">id</span><span class="dl">'</span><span class="p">)</span> <span class="nx">id</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Product</span><span class="o">></span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">productsService</span><span class="p">.</span><span class="nx">findOne</span><span class="p">(</span><span class="nx">id</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">@</span><span class="nd">UseGuards</span><span class="p">(</span><span class="nx">JwtAuthGuard</span><span class="p">)</span>
<span class="p">@</span><span class="nd">Patch</span><span class="p">(</span><span class="dl">'</span><span class="s1">:id</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">update</span><span class="p">(</span>
<span class="p">@</span><span class="nd">Param</span><span class="p">(</span><span class="dl">'</span><span class="s1">id</span><span class="dl">'</span><span class="p">)</span> <span class="nx">id</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="p">@</span><span class="nd">Body</span><span class="p">()</span> <span class="nx">updateProductDto</span><span class="p">:</span> <span class="nx">UpdateProductDto</span><span class="p">,</span>
<span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">UpdateResult</span><span class="o">></span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">productsService</span><span class="p">.</span><span class="nx">update</span><span class="p">(</span><span class="nx">id</span><span class="p">,</span> <span class="nx">updateProductDto</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">@</span><span class="nd">UseGuards</span><span class="p">(</span><span class="nx">JwtAuthGuard</span><span class="p">)</span>
<span class="p">@</span><span class="nd">Delete</span><span class="p">(</span><span class="dl">'</span><span class="s1">:id</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">remove</span><span class="p">(@</span><span class="nd">Param</span><span class="p">(</span><span class="dl">'</span><span class="s1">id</span><span class="dl">'</span><span class="p">)</span> <span class="nx">id</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">DeleteResult</span><span class="o">></span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">productsService</span><span class="p">.</span><span class="nx">remove</span><span class="p">(</span><span class="nx">id</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<h3 id="unit-tests">Unit Tests</h3>
<p>Here are the unit tests for the service. The describe blocks explain pretty much what the given spec is intended to perform. As in previous tests, we are mocking a lot of objects using Jest:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/products/spec/products.service.spec.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/products/spec/products.service.spec.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/products/spec/products.service.spec.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/products/spec/products.service.spec.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Test</span><span class="p">,</span> <span class="nx">TestingModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/testing</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ProductsService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../products.service</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">getRepositoryToken</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Product</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../entities/product.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CreateProductDto</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../dto/create-product.dto</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">DeleteResult</span><span class="p">,</span> <span class="nx">UpdateResult</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">UpdateProductDto</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../dto/update-product.dto</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">ProductsService</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">service</span><span class="p">:</span> <span class="nx">ProductsService</span><span class="p">;</span>
<span class="nx">beforeEach</span><span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">createMock</span> <span class="o">=</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">((</span><span class="na">dto</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">dto</span><span class="p">;</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">saveMock</span> <span class="o">=</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">((</span><span class="na">dto</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">dto</span><span class="p">;</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">findOneMock</span> <span class="o">=</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">((</span><span class="na">dto</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">dto</span><span class="p">;</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">mockRepository</span> <span class="o">=</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">().</span><span class="nx">mockImplementation</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">create</span><span class="p">:</span> <span class="nx">createMock</span><span class="p">,</span>
<span class="na">save</span><span class="p">:</span> <span class="nx">saveMock</span><span class="p">,</span>
<span class="na">findOne</span><span class="p">:</span> <span class="nx">findOneMock</span><span class="p">,</span>
<span class="p">};</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="na">module</span><span class="p">:</span> <span class="nx">TestingModule</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">Test</span><span class="p">.</span><span class="nx">createTestingModule</span><span class="p">({</span>
<span class="na">providers</span><span class="p">:</span> <span class="p">[</span>
<span class="nx">ProductsService</span><span class="p">,</span>
<span class="p">{</span>
<span class="na">provide</span><span class="p">:</span> <span class="nx">getRepositoryToken</span><span class="p">(</span><span class="nx">Product</span><span class="p">),</span>
<span class="na">useClass</span><span class="p">:</span> <span class="nx">mockRepository</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">],</span>
<span class="p">}).</span><span class="nx">compile</span><span class="p">();</span>
<span class="nx">service</span> <span class="o">=</span> <span class="kr">module</span><span class="p">.</span><span class="kd">get</span><span class="o"><</span><span class="nx">ProductsService</span><span class="o">></span><span class="p">(</span><span class="nx">ProductsService</span><span class="p">);</span>
<span class="p">});</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should be defined</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">expect</span><span class="p">(</span><span class="nx">service</span><span class="p">).</span><span class="nx">toBeDefined</span><span class="p">();</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">findAll</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should return an array of products</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">result</span><span class="p">:</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Product</span><span class="p">[]</span><span class="o">></span><span class="p">;</span>
<span class="nx">jest</span><span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">service</span><span class="p">,</span> <span class="dl">'</span><span class="s1">findAll</span><span class="dl">'</span><span class="p">).</span><span class="nx">mockImplementation</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">result</span><span class="p">);</span>
<span class="nx">expect</span><span class="p">(</span><span class="k">await</span> <span class="nx">service</span><span class="p">.</span><span class="nx">findAll</span><span class="p">()).</span><span class="nx">toBe</span><span class="p">(</span><span class="nx">result</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">findOne</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should return a single product</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">product</span><span class="p">:</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Product</span><span class="o">></span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">testProductId</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">2173dd22-42ed-4091-bb46-aaca401efa45</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">jest</span><span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">service</span><span class="p">,</span> <span class="dl">'</span><span class="s1">findOne</span><span class="dl">'</span><span class="p">).</span><span class="nx">mockImplementation</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">product</span><span class="p">);</span>
<span class="nx">expect</span><span class="p">(</span><span class="k">await</span> <span class="nx">service</span><span class="p">.</span><span class="nx">findOne</span><span class="p">(</span><span class="nx">testProductId</span><span class="p">)).</span><span class="nx">toBe</span><span class="p">(</span><span class="nx">product</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">create</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should return a new product</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">product</span><span class="p">:</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Product</span><span class="o">></span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">testCreateProductDto</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">CreateProductDto</span><span class="p">();</span>
<span class="nx">jest</span><span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">service</span><span class="p">,</span> <span class="dl">'</span><span class="s1">create</span><span class="dl">'</span><span class="p">).</span><span class="nx">mockImplementation</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">product</span><span class="p">);</span>
<span class="nx">expect</span><span class="p">(</span><span class="k">await</span> <span class="nx">service</span><span class="p">.</span><span class="nx">create</span><span class="p">(</span><span class="nx">testCreateProductDto</span><span class="p">)).</span><span class="nx">toBe</span><span class="p">(</span><span class="nx">product</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">update</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should return an update result</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">updateResult</span><span class="p">:</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">UpdateResult</span><span class="o">></span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">testUpdateProductDto</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">UpdateProductDto</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">testProductId</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">2173dd22-42ed-4091-bb46-aaca401efa45</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">jest</span><span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">service</span><span class="p">,</span> <span class="dl">'</span><span class="s1">update</span><span class="dl">'</span><span class="p">).</span><span class="nx">mockImplementation</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">updateResult</span><span class="p">);</span>
<span class="nx">expect</span><span class="p">(</span><span class="k">await</span> <span class="nx">service</span><span class="p">.</span><span class="nx">update</span><span class="p">(</span><span class="nx">testProductId</span><span class="p">,</span> <span class="nx">testUpdateProductDto</span><span class="p">)).</span><span class="nx">toBe</span><span class="p">(</span>
<span class="nx">updateResult</span><span class="p">,</span>
<span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">remove</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should return an delete result</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">deleteResult</span><span class="p">:</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">DeleteResult</span><span class="o">></span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">testProductId</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">2173dd22-42ed-4091-bb46-aaca401efa45</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">jest</span><span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">service</span><span class="p">,</span> <span class="dl">'</span><span class="s1">remove</span><span class="dl">'</span><span class="p">).</span><span class="nx">mockImplementation</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">deleteResult</span><span class="p">);</span>
<span class="nx">expect</span><span class="p">(</span><span class="k">await</span> <span class="nx">service</span><span class="p">.</span><span class="nx">remove</span><span class="p">(</span><span class="nx">testProductId</span><span class="p">)).</span><span class="nx">toBe</span><span class="p">(</span><span class="nx">deleteResult</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">checkIfProductsExists</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should return an array of products</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">result</span><span class="p">:</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Product</span><span class="p">[]</span><span class="o">></span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">testProductIds</span> <span class="o">=</span> <span class="p">[</span>
<span class="dl">'</span><span class="s1">2173dd22-42ed-4091-bb46-aaca401efa45</span><span class="dl">'</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">1884c5c2-ccab-45ed-b005-19769ffa0697</span><span class="dl">'</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">da2e4c9a-439e-47f5-a289-0b193970be04</span><span class="dl">'</span><span class="p">,</span>
<span class="p">];</span>
<span class="nx">jest</span>
<span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">service</span><span class="p">,</span> <span class="dl">'</span><span class="s1">checkIfProductsExist</span><span class="dl">'</span><span class="p">)</span>
<span class="p">.</span><span class="nx">mockImplementation</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">result</span><span class="p">);</span>
<span class="nx">expect</span><span class="p">(</span><span class="k">await</span> <span class="nx">service</span><span class="p">.</span><span class="nx">checkIfProductsExist</span><span class="p">(</span><span class="nx">testProductIds</span><span class="p">)).</span><span class="nx">toBe</span><span class="p">(</span><span class="nx">result</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p">});</span></code></pre></figure>
<p>Here is the spec passing:</p>
<p><img src="/assets/storefront/28-products-service-spec.png" alt="Products Service Spec" /></p>
<p>Here are the unit tests for the controller. We have only added a single spec here to test that the controller has been defined, since the bulk of the controller tests will be done later via end to end tests:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/products/spec/products.controller.spec.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/products/spec/products.controller.spec.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/products/spec/products.controller.spec.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/products/spec/products.controller.spec.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Test</span><span class="p">,</span> <span class="nx">TestingModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/testing</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ProductsController</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../products.controller</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ProductsService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../products.service</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">ProductsController</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">controller</span><span class="p">:</span> <span class="nx">ProductsController</span><span class="p">;</span>
<span class="nx">beforeEach</span><span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="na">module</span><span class="p">:</span> <span class="nx">TestingModule</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">Test</span><span class="p">.</span><span class="nx">createTestingModule</span><span class="p">({</span>
<span class="na">controllers</span><span class="p">:</span> <span class="p">[</span><span class="nx">ProductsController</span><span class="p">],</span>
<span class="na">providers</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="na">provide</span><span class="p">:</span> <span class="nx">ProductsService</span><span class="p">,</span>
<span class="na">useValue</span><span class="p">:</span> <span class="p">{},</span>
<span class="p">},</span>
<span class="p">],</span>
<span class="p">}).</span><span class="nx">compile</span><span class="p">();</span>
<span class="nx">controller</span> <span class="o">=</span> <span class="kr">module</span><span class="p">.</span><span class="kd">get</span><span class="o"><</span><span class="nx">ProductsController</span><span class="o">></span><span class="p">(</span><span class="nx">ProductsController</span><span class="p">);</span>
<span class="p">});</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should be defined</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">expect</span><span class="p">(</span><span class="nx">controller</span><span class="p">).</span><span class="nx">toBeDefined</span><span class="p">();</span>
<span class="p">});</span>
<span class="p">});</span></code></pre></figure>
<p>Here is the spec passing:</p>
<p><img src="/assets/storefront/27-products-controller-spec.png" alt="Products Controller Spec" /></p>
<h3 id="postman-tests">Postman Tests</h3>
<p>As usual, for good measure we carry out some Postman tests on our endpoints to see them in action:</p>
<p>Get Products</p>
<p><img src="/assets/storefront/31-get-products.png" alt="Get products" /></p>
<p>Fetch a single product</p>
<p><img src="/assets/storefront/32-get-single-product.png" alt="Fetch a single product" /></p>
<p>Creating a product</p>
<p><img src="/assets/storefront/29-create-product.png" alt="Create product" /></p>
<p>Update a product</p>
<p><img src="/assets/storefront/33-update-product.png" alt="Update product" /></p>
<p>Delete a product</p>
<p><img src="/assets/storefront/30-delete-product.png" alt="Delete product" /></p>Isaiah MucheneToday we shall be creating the products module for our backend application, starting as usual with the products module. In case of any issues, you can refer to my Github repositoryNest.js Authentication2022-07-23T16:48:00+03:002022-07-23T16:48:00+03:00/2022/07/auth-module<div class="card seriesNote">
<p class="text-center">
This article is <strong>Part 6</strong> in a <strong>10 Part</strong> Series.
</p>
<ul class="list-group list-group-flush">
<li class="list-group-item"> Part 1 -
<a href="/2022/04/bootstrapping-nestjs-ubuntu">Bootstrapping a Nest.js Project on Ubuntu</a>
</li>
<li class="list-group-item"> Part 2 -
<a href="/2022/04/create-nestjs-migrations">Create Nest.js Migrations</a>
</li>
<li class="list-group-item"> Part 3 -
<a href="/2022/04/create-nestjs-crud-resources">Create Nest.js CRUD Resources</a>
</li>
<li class="list-group-item"> Part 4 -
<a href="/2022/06/create-nestjs-seeders">Create Nest.js Seeders</a>
</li>
<li class="list-group-item"> Part 5 -
<a href="/2022/07/customer-module">Create a Customer Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 6 -
This Article
</li>
<li class="list-group-item"> Part 7 -
<a href="/2022/07/products-module">Create a Products Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 8 -
<a href="/2022/07/stripe-module">Create a Stripe Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 9 -
<a href="/2022/08/orders-module">Create an Orders Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 10 -
<a href="/2022/08/end-to-end-tests">End to End Tests for our Storefront Backend</a>
</li>
</ul>
</div>
<p><br /></p>
<p>Continuing with the series, in this iteration of the tutorial we shall be focusing on implementing a way to keep track of our customers, as well as some basic security for our application. For starters, we shall add functionality for registering customers, logging them in as well as logging them out. We will need this as each order in our database will be attached to a single customer. All the above functionality will be contained within an application module.</p>
<p>In the typescript/javascript world, the most popular method for authenticating users of a system is through <a href="https://jwt.io/introduction/">JWT authentication</a>. In this method, once a user successfully logs in to the application, a JWT authentication token is sent to the user’s browser via a cookie in the server response. This token is usually valid for a short period of time, and the user can utilise it to make subsequent requests to access protected resources on the application.</p>
<p>In case of any issues, you can refer to my Github repository <a href="https://github.com/imuchene/storefront-backend">here</a>.</p>
<h3 id="authentication-flow">Authentication Flow</h3>
<p>Here’s a sequence diagram of our authentication flow, from registration to login to logout:</p>
<p><img src="/assets/storefront/25-Auth-Token-Flow.png" alt="Authentication Flow" /></p>
<p>It might look intimidating at first, but we shall tackle all the various stages above step by step.</p>
<h3 id="install-redis">Install Redis</h3>
<p>We’ll be using redis to store our refresh tokens (until the user logs out). We will therefore need to install it first. Here’s how to do so on Ubuntu:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt <span class="nb">install </span>redis-server
</code></pre></div></div>
<p>It is strongly recommended to secure redis post installation by enforcing authentication. In order to so, check out the tutorial <a href="https://www.digitalocean.com/community/tutorials/how-to-install-and-secure-redis-on-ubuntu-22-04#step-4-configuring-a-redis-password">here</a>, as well as the first comment in the tutorial.</p>
<h3 id="install-install-bcrypt">Install Install bcrypt</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i @types/bcrypt bcrypt <span class="nt">--save</span>
</code></pre></div></div>
<h3 id="install-passport-and-jwt">Install passport and JWT</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i @nestjs/passport @nestjs/jwt passport passport-jwt @types/express @types/passport-jwt <span class="nt">--save</span>
</code></pre></div></div>
<p>In order to secure our JWT tokens from being tampered with, we shall create two RSA keypairs to sign our JWT tokens, which is the <a href="https://docs.nestjs.com/security/authentication#jwt-functionality">approach recommended</a> by the Nest.js core developers.</p>
<h3 id="generate-jwt-access-token-keypairs">Generate JWT Access Token keypairs</h3>
<p>Within the project root, run to create the keys directory:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir </span>rsa-keys
</code></pre></div></div>
<p>Use the <em>ssh-keygen</em> command to generate a 4096 bit RSA private key in the PEM format:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh-keygen <span class="nt">-t</span> rsa <span class="nt">-b</span> 4096 <span class="nt">-m</span> PEM <span class="nt">-f</span> jwtRS256.key
</code></pre></div></div>
<p>Use the <em>openssl</em> command to generate a correspoding RSA public key also in PEM format using the previously generated private key as an input:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl rsa <span class="nt">-in</span> jwtRS256.key <span class="nt">-pubout</span> <span class="nt">-outform</span> PEM <span class="nt">-out</span> jwtRS256.key.pub
</code></pre></div></div>
<p>Check if the private key has been successfully created by printing it out to the console:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cat </span>jwtRS256.key
</code></pre></div></div>
<p>Check if the public key has been successfully created by printing it out to the console:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cat </span>jwtRS256.key.pub
</code></pre></div></div>
<p>Rename the private key to a more memorable name:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mv</span> <span class="nt">-v</span> jwtRS256.key access-token-private-key.key
</code></pre></div></div>
<p>Rename the public key to a more memorable name:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mv</span> <span class="nt">-v</span> jwtRS256.key.pub access-token-public-key.pub
</code></pre></div></div>
<p>Clear the console:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>clear
</code></pre></div></div>
<p>This last step is recommended since you would not want an attacker being able to view a copy of your keys via the terminal.</p>
<h3 id="generate-jwt-refresh-token-keypairs">Generate JWT Refresh Token keypairs</h3>
<p>Repeat the same steps as above when creating the access token keypairs in order to create the refresh token keypairs Within the same directory. This time rename the outputted files to <em>refresh-token-private-key</em> and <em>refresh-token-public-key</em> respectively:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd </span>rsa-keys
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh-keygen <span class="nt">-t</span> rsa <span class="nt">-b</span> 4096 <span class="nt">-m</span> PEM <span class="nt">-f</span> jwtRS256.key
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl rsa <span class="nt">-in</span> jwtRS256.key <span class="nt">-pubout</span> <span class="nt">-outform</span> PEM <span class="nt">-out</span> jwtRS256.key.pub
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cat </span>jwtRS256.key
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cat </span>jwtRS256.key.pub
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mv</span> <span class="nt">-v</span> jwtRS256.key refresh-token-private-key.key
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mv</span> <span class="nt">-v</span> jwtRS256.key.pub refresh-token-public-key.pub
</code></pre></div></div>
<p>Clear the console:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>clear
</code></pre></div></div>
<p>This last step is recommended since you would not want an attacker being able to view a copy of your keys via the terminal.</p>
<h3 id="add-new-configuration-to-app-module">Add new configuration to app module</h3>
<p>Here is how my updated app.module file looks like. I have imported the <em>JwtModule</em> from <em>@nestjs/jwt</em>, <em>ConfigModule</em> and <em>ConfigService</em> from <em>@nestjs/config</em>, <em>CustomersModule</em> (that we previously created) as well as the <em>fs</em> utility (in order to access files in our local filesystem). I also created and imported some strategy files which I will include shortly.</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/auth.module.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/auth/auth.module.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/auth.module.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/auth.module.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Module</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AuthService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./auth.service</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AuthController</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./auth.controller</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CustomersModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../customers/customers.module</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">JwtModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/jwt</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ConfigModule</span><span class="p">,</span> <span class="nx">ConfigService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/config</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">JwtStrategy</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./strategies/jwt.strategy</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">LocalStrategy</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./strategies/local.strategy</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">RefreshStrategy</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./strategies/refresh.strategy</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">fs</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">fs</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Module</span><span class="p">({</span>
<span class="na">imports</span><span class="p">:</span> <span class="p">[</span>
<span class="nx">CustomersModule</span><span class="p">,</span>
<span class="nx">JwtModule</span><span class="p">.</span><span class="nx">registerAsync</span><span class="p">({</span>
<span class="na">imports</span><span class="p">:</span> <span class="p">[</span><span class="nx">ConfigModule</span><span class="p">],</span>
<span class="na">inject</span><span class="p">:</span> <span class="p">[</span><span class="nx">ConfigService</span><span class="p">],</span>
<span class="na">useFactory</span><span class="p">:</span> <span class="k">async</span> <span class="p">(</span><span class="na">configService</span><span class="p">:</span> <span class="nx">ConfigService</span><span class="p">)</span> <span class="o">=></span> <span class="p">({</span>
<span class="na">privateKey</span><span class="p">:</span> <span class="nx">fs</span>
<span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span>
<span class="nx">configService</span><span class="p">.</span><span class="kd">get</span><span class="o"><</span><span class="kr">string</span><span class="o">></span><span class="p">(</span><span class="dl">'</span><span class="s1">JWT_ACCESS_TOKEN_PRIVATE_KEY</span><span class="dl">'</span><span class="p">),</span>
<span class="p">)</span>
<span class="p">.</span><span class="nx">toString</span><span class="p">(),</span>
<span class="na">publicKey</span><span class="p">:</span> <span class="nx">fs</span>
<span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span>
<span class="nx">configService</span><span class="p">.</span><span class="kd">get</span><span class="o"><</span><span class="kr">string</span><span class="o">></span><span class="p">(</span><span class="dl">'</span><span class="s1">JWT_ACCESS_TOKEN_PUBLIC_KEY</span><span class="dl">'</span><span class="p">),</span>
<span class="p">)</span>
<span class="p">.</span><span class="nx">toString</span><span class="p">(),</span>
<span class="na">signOptions</span><span class="p">:</span> <span class="p">{</span>
<span class="na">expiresIn</span><span class="p">:</span> <span class="nx">configService</span><span class="p">.</span><span class="kd">get</span><span class="o"><</span><span class="kr">string</span><span class="o">></span><span class="p">(</span>
<span class="dl">'</span><span class="s1">JWT_ACCESS_TOKEN_EXPIRATION_TIME</span><span class="dl">'</span><span class="p">,</span>
<span class="p">),</span>
<span class="na">algorithm</span><span class="p">:</span> <span class="dl">'</span><span class="s1">RS256</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">}),</span>
<span class="p">}),</span>
<span class="p">],</span>
<span class="na">providers</span><span class="p">:</span> <span class="p">[</span><span class="nx">AuthService</span><span class="p">,</span> <span class="nx">JwtStrategy</span><span class="p">,</span> <span class="nx">LocalStrategy</span><span class="p">,</span> <span class="nx">RefreshStrategy</span><span class="p">],</span>
<span class="na">controllers</span><span class="p">:</span> <span class="p">[</span><span class="nx">AuthController</span><span class="p">],</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">AuthModule</span> <span class="p">{}</span></code></pre></figure>
<h3 id="update-secrets-file">Update Secrets File</h3>
<p>Don’t forget to add the references to the RSA keys generated above to your secrets file (.env), as well as the redis configuration.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Access Token Secrets</span>
<span class="nv">JWT_ACCESS_TOKEN_PUBLIC_KEY</span><span class="o">=</span><span class="s1">'rsa-keys/access-token-public-key.pub'</span>
<span class="nv">JWT_ACCESS_TOKEN_PRIVATE_KEY</span><span class="o">=</span><span class="s1">'rsa-keys/access-token-private-key.key'</span>
<span class="nv">JWT_ACCESS_TOKEN_EXPIRATION_TIME</span><span class="o">=</span>600s
<span class="c"># Cookie Secret</span>
<span class="nv">COOKIE_SECRET</span><span class="o">=</span>thisisateststringforcookies
<span class="c"># Redis Configuration</span>
<span class="nv">REDIS_HOST</span><span class="o">=</span>localhost
<span class="nv">REDIS_PORT</span><span class="o">=</span>6379
<span class="nv">REDIS_DB</span><span class="o">=</span>0
<span class="nv">REDIS_USERNAME</span><span class="o">=</span>your_redis_username
<span class="nv">REDIS_PASSWORD</span><span class="o">=</span>your_redis_password
<span class="nv">REDIS_PREFIX</span><span class="o">=</span><span class="s1">'development:'</span>
<span class="c"># Refresh Token Secrets</span>
<span class="nv">JWT_REFRESH_TOKEN_PUBLIC_KEY</span><span class="o">=</span><span class="s1">'rsa-keys/refresh-token-public-key.pub'</span>
<span class="nv">JWT_REFRESH_TOKEN_PRIVATE_KEY</span><span class="o">=</span><span class="s1">'rsa-keys/refresh-token-private-key.key'</span>
<span class="nv">JWT_REFRESH_TOKEN_EXPIRATION_TIME</span><span class="o">=</span>600s
</code></pre></div></div>
<h3 id="update-and-validate-environment-variables">Update and validate environment variables</h3>
<p>Don’t forget to also update and validate your environment variables:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/common/config/env.validation.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/common/config/env.validation.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/common/config/env.validation.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/common/config/env.validation.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="p">@</span><span class="nd">IsString</span><span class="p">()</span>
<span class="nx">JWT_ACCESS_TOKEN_EXPIRATION_TIME</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">IsString</span><span class="p">()</span>
<span class="nx">COOKIE_SECRET</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">IsString</span><span class="p">()</span>
<span class="nx">REDIS_HOST</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">IsNumber</span><span class="p">()</span>
<span class="nx">REDIS_PORT</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">@</span><span class="nd">IsNumber</span><span class="p">()</span>
<span class="nx">REDIS_DB</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">@</span><span class="nd">IsString</span><span class="p">()</span>
<span class="nx">REDIS_USERNAME</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">IsString</span><span class="p">()</span>
<span class="nx">REDIS_PASSWORD</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">IsString</span><span class="p">()</span>
<span class="nx">REDIS_PREFIX</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">IsString</span><span class="p">()</span>
<span class="nx">JWT_REFRESH_TOKEN_EXPIRATION_TIME</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">IsString</span><span class="p">()</span>
<span class="nx">JWT_ACCESS_TOKEN_PUBLIC_KEY</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">IsString</span><span class="p">()</span>
<span class="nx">JWT_ACCESS_TOKEN_PRIVATE_KEY</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">IsString</span><span class="p">()</span>
<span class="nx">JWT_REFRESH_TOKEN_PUBLIC_KEY</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">IsString</span><span class="p">()</span>
<span class="nx">JWT_REFRESH_TOKEN_PRIVATE_KEY</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span></code></pre></figure>
<h3 id="add-cookie-secrets-to-the-maints-file">Add Cookie Secrets to the main.ts file</h3>
<p>Since we’ll be signing our cookies to prevent them from being tampered with, we’ll have to add a signing key as a parameter to cookie parser in our main.ts file:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/main.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/main.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/main.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/main.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">ValidationPipe</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ConfigService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/config</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">NestFactory</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/core</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AppModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./app.module</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">cookieParser</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">cookie-parser</span><span class="dl">'</span><span class="p">;</span>
<span class="k">async</span> <span class="kd">function</span> <span class="nx">bootstrap</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">NestFactory</span><span class="p">.</span><span class="nx">create</span><span class="p">(</span><span class="nx">AppModule</span><span class="p">);</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">useGlobalPipes</span><span class="p">(</span><span class="k">new</span> <span class="nx">ValidationPipe</span><span class="p">());</span>
<span class="kd">const</span> <span class="nx">configService</span> <span class="o">=</span> <span class="nx">app</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">ConfigService</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">port</span> <span class="o">=</span> <span class="nx">configService</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">PORT</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">cookieParser</span><span class="p">(</span><span class="nx">configService</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">COOKIE_SECRET</span><span class="dl">'</span><span class="p">)));</span>
<span class="k">await</span> <span class="nx">app</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="nx">port</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">bootstrap</span><span class="p">();</span></code></pre></figure>
<h3 id="strategies">Strategies</h3>
<p>In the Typescript and Node.js world, authentication is done by means of strategies. Since Nest.js doesn’t enforce any default authentication strategy, we will have to implement one for ourseleves from scratch. In order to do so, we will be using the passport module, which we had added earlier on in our project. We will be creating three different strategies to handle authentication in our application: local, refresh and JWT.</p>
<p>The local strategy:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/strategies/local.strategy.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/auth/strategies/local.strategy.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/strategies/local.strategy.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/strategies/local.strategy.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Injectable</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">PassportStrategy</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/passport</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Strategy</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">passport-local</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Customer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../customers/entities/customer.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AuthService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../auth.service</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Injectable</span><span class="p">()</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">LocalStrategy</span> <span class="kd">extends</span> <span class="nx">PassportStrategy</span><span class="p">(</span><span class="nx">Strategy</span><span class="p">,</span> <span class="dl">'</span><span class="s1">local</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span><span class="k">private</span> <span class="k">readonly</span> <span class="nx">authService</span><span class="p">:</span> <span class="nx">AuthService</span><span class="p">)</span> <span class="p">{</span>
<span class="k">super</span><span class="p">({</span>
<span class="na">usernameField</span><span class="p">:</span> <span class="dl">'</span><span class="s1">email</span><span class="dl">'</span><span class="p">,</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="k">async</span> <span class="nx">validate</span><span class="p">(</span><span class="nx">email</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Customer</span><span class="o">></span> <span class="p">{</span>
<span class="k">return</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">authService</span><span class="p">.</span><span class="nx">getAuthenticatedCustomer</span><span class="p">(</span><span class="nx">email</span><span class="p">,</span> <span class="nx">password</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>This strategy extends from the parent PassportStrategy, and it directs the passport module on what to do once a user supplies us with a username/password combination. By default, the local stategy expects a username/password combination, but we instead direct it to anticipate an email field in place of a username. Next, in order to validate our customer, we direct it to the getAuthenticatedCustomer method in our AuthService.</p>
<p>The JWT strategy:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/strategies/jwt.strategy.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/auth/strategies/jwt.strategy.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/strategies/jwt.strategy.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/strategies/jwt.strategy.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Injectable</span><span class="p">,</span> <span class="nx">UnauthorizedException</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ConfigService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/config</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">PassportStrategy</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/passport</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ExtractJwt</span><span class="p">,</span> <span class="nx">Strategy</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">passport-jwt</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CustomersService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../customers/customers.service</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Request</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">express</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">JwtTokenPayload</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../interfaces/jwt-payload.interface</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Customer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../customers/entities/customer.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">SecretData</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../interfaces/secret-data.interface</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">fs</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">fs</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Injectable</span><span class="p">()</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">JwtStrategy</span> <span class="kd">extends</span> <span class="nx">PassportStrategy</span><span class="p">(</span><span class="nx">Strategy</span><span class="p">,</span> <span class="dl">'</span><span class="s1">jwt</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="nx">customersService</span><span class="p">:</span> <span class="nx">CustomersService</span><span class="p">,</span>
<span class="k">readonly</span> <span class="nx">configService</span><span class="p">:</span> <span class="nx">ConfigService</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
<span class="k">super</span><span class="p">({</span>
<span class="na">jwtFromRequest</span><span class="p">:</span> <span class="nx">ExtractJwt</span><span class="p">.</span><span class="nx">fromExtractors</span><span class="p">([</span>
<span class="p">(</span><span class="na">request</span><span class="p">:</span> <span class="nx">Request</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="na">data</span><span class="p">:</span> <span class="nx">SecretData</span> <span class="o">=</span> <span class="nx">request</span><span class="p">?.</span><span class="nx">signedCookies</span><span class="p">[</span><span class="dl">'</span><span class="s1">auth-cookie</span><span class="dl">'</span><span class="p">];</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">data</span><span class="p">.</span><span class="nx">jwtAccessToken</span><span class="p">;</span>
<span class="p">},</span>
<span class="p">]),</span>
<span class="na">secretOrKey</span><span class="p">:</span> <span class="nx">fs</span>
<span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span><span class="nx">configService</span><span class="p">.</span><span class="kd">get</span><span class="o"><</span><span class="kr">string</span><span class="o">></span><span class="p">(</span><span class="dl">'</span><span class="s1">JWT_ACCESS_TOKEN_PUBLIC_KEY</span><span class="dl">'</span><span class="p">))</span>
<span class="p">.</span><span class="nx">toString</span><span class="p">(),</span>
<span class="na">ignoreExpiration</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="k">async</span> <span class="nx">validate</span><span class="p">(</span><span class="nx">payload</span><span class="p">:</span> <span class="nx">JwtTokenPayload</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Customer</span><span class="o">></span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">payload</span> <span class="o">===</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">UnauthorizedException</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">customersService</span><span class="p">.</span><span class="nx">getById</span><span class="p">(</span><span class="nx">payload</span><span class="p">.</span><span class="nx">customerId</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>This strategy directs our application on how to validate JWT tokens - which the API will be sending and receiving by means of signed cookies. First, the JWT token will be fetched from the cookie through the ExtractJwt library. We will be using the public key that we created previously to decode the JWT access token. By default, the passport module will return a 401 Unauthorized error if a JWT token is expired/invalid, and it will do this behind the scenes. In the validate method, we instruct passport to return a customer object in the event the JWT token received via the cookie is valid. In the event no token exists, to throw an unauthorized exception.</p>
<p>The Refresh strategy:</p>
<p>This strategy will be used to validate refresh tokens. It is very similar to the JWT strategy, however, with a few differences:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/strategies/refresh.strategy.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/auth/strategies/refresh.strategy.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/strategies/refresh.strategy.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/strategies/refresh.strategy.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">BadRequestException</span><span class="p">,</span> <span class="nx">Injectable</span><span class="p">,</span> <span class="nx">Logger</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ConfigService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/config</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">PassportStrategy</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/passport</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ExtractJwt</span><span class="p">,</span> <span class="nx">Strategy</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">passport-jwt</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Request</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">express</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">SecretData</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../interfaces/secret-data.interface</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AuthService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../auth.service</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Customer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../customers/entities/customer.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">JwtTokenPayload</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../interfaces/jwt-payload.interface</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">fs</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">fs</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Injectable</span><span class="p">()</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">RefreshStrategy</span> <span class="kd">extends</span> <span class="nx">PassportStrategy</span><span class="p">(</span><span class="nx">Strategy</span><span class="p">,</span> <span class="dl">'</span><span class="s1">refresh</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span>
<span class="k">readonly</span> <span class="nx">configService</span><span class="p">:</span> <span class="nx">ConfigService</span><span class="p">,</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="nx">authService</span><span class="p">:</span> <span class="nx">AuthService</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
<span class="k">super</span><span class="p">({</span>
<span class="na">ignoreExpiration</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">passReqToCallback</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">secretOrKey</span><span class="p">:</span> <span class="nx">fs</span>
<span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span><span class="nx">configService</span><span class="p">.</span><span class="kd">get</span><span class="o"><</span><span class="kr">string</span><span class="o">></span><span class="p">(</span><span class="dl">'</span><span class="s1">JWT_REFRESH_TOKEN_PUBLIC_KEY</span><span class="dl">'</span><span class="p">))</span>
<span class="p">.</span><span class="nx">toString</span><span class="p">(),</span>
<span class="na">jwtFromRequest</span><span class="p">:</span> <span class="nx">ExtractJwt</span><span class="p">.</span><span class="nx">fromExtractors</span><span class="p">([</span>
<span class="p">(</span><span class="na">request</span><span class="p">:</span> <span class="nx">Request</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="na">data</span><span class="p">:</span> <span class="nx">SecretData</span> <span class="o">=</span> <span class="nx">request</span><span class="p">?.</span><span class="nx">signedCookies</span><span class="p">[</span><span class="dl">'</span><span class="s1">auth-cookie</span><span class="dl">'</span><span class="p">];</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">data</span><span class="p">.</span><span class="nx">jwtRefreshToken</span><span class="p">;</span>
<span class="p">},</span>
<span class="p">]),</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="k">async</span> <span class="nx">validate</span><span class="p">(</span><span class="nx">req</span><span class="p">:</span> <span class="nx">Request</span><span class="p">,</span> <span class="nx">payload</span><span class="p">:</span> <span class="nx">JwtTokenPayload</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Customer</span><span class="o">></span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">payload</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">BadRequestException</span><span class="p">(</span><span class="dl">'</span><span class="s1">no token payload</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="na">data</span><span class="p">:</span> <span class="nx">SecretData</span> <span class="o">=</span> <span class="nx">req</span><span class="p">?.</span><span class="nx">signedCookies</span><span class="p">[</span><span class="dl">'</span><span class="s1">auth-cookie</span><span class="dl">'</span><span class="p">];</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">data</span><span class="p">.</span><span class="nx">jwtRefreshToken</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">BadRequestException</span><span class="p">(</span><span class="dl">'</span><span class="s1">invalid refresh token</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">customer</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">authService</span><span class="p">.</span><span class="nx">validateJwtRefreshToken</span><span class="p">(</span>
<span class="nx">payload</span><span class="p">.</span><span class="nx">customerId</span><span class="p">,</span>
<span class="nx">data</span><span class="p">.</span><span class="nx">jwtRefreshToken</span><span class="p">,</span>
<span class="nx">data</span><span class="p">.</span><span class="nx">refreshTokenId</span><span class="p">,</span>
<span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">customer</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">BadRequestException</span><span class="p">(</span><span class="dl">'</span><span class="s1">token expired</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">customer</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>Since we don’t intend to expire our refresh tokens, we tell the Passport to ignore the expiry of our JWT tokens. To avoid errors when parsing a refresh token from a cookie such as the error below:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>Nest] 284242 - 06/27/2022, 4:45:16 PM ERROR <span class="o">[</span>ExceptionsHandler] Cannot <span class="nb">read </span>properties of undefined <span class="o">(</span>reading <span class="s1">'auth-cookie'</span><span class="o">)</span>
TypeError: Cannot <span class="nb">read </span>properties of undefined <span class="o">(</span>reading <span class="s1">'auth-cookie'</span><span class="o">)</span>
at RefreshStrategy.validate <span class="o">(</span>/home/isaiah/Projects/nest_projects/storefront-backend/src/modules/auth/strategies/refresh.strategy.ts:40:48<span class="o">)</span>
at RefreshStrategy.<anonymous> <span class="o">(</span>/home/isaiah/Projects/nest_projects/storefront-backend/node_modules/@nestjs/passport/dist/passport/passport.strategy.js:20:55<span class="o">)</span>
at Generator.next <span class="o">(</span><anonymous><span class="o">)</span>
at /home/isaiah/Projects/nest_projects/storefront-backend/node_modules/@nestjs/passport/dist/passport/passport.strategy.js:8:71
at new Promise <span class="o">(</span><anonymous><span class="o">)</span>
at __awaiter <span class="o">(</span>/home/isaiah/Projects/nest_projects/storefront-backend/node_modules/@nestjs/passport/dist/passport/passport.strategy.js:4:12<span class="o">)</span>
at RefreshStrategy.callback <span class="o">[</span>as _verify] <span class="o">(</span>/home/isaiah/Projects/nest_projects/storefront-backend/node_modules/@nestjs/passport/dist/passport/passport.strategy.js:17:45<span class="o">)</span>
at /home/isaiah/Projects/nest_projects/storefront-backend/node_modules/passport-jwt/lib/strategy.js:123:34
at /home/isaiah/Projects/nest_projects/storefront-backend/node_modules/jsonwebtoken/verify.js:223:12
at getSecret <span class="o">(</span>/home/isaiah/Projects/nest_projects/storefront-backend/node_modules/jsonwebtoken/verify.js:90:14<span class="o">)</span>
</code></pre></div></div>
<p>We have to set <em>passReqCallback</em> to <strong>true</strong>. In a similar fashion to the JWT strategy, we use the refresh token public key to decrypt the token. Lastly, we extract the refresh token from the cookie and return it as a parameter.</p>
<p>Here are the authentication interfaces:</p>
<p>The Jwt payload interface the data that is returned once a JWT Access/Refresh token is decrypted.</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/interfaces/jwt-payload.interface.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/auth/interfaces/jwt-payload.interface.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/interfaces/jwt-payload.interface.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/interfaces/jwt-payload.interface.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">export</span> <span class="kr">interface</span> <span class="nx">JwtTokenPayload</span> <span class="p">{</span>
<span class="nl">customerId</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>The secret data interface represents the data that will be encoded within the signed cookie.</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/interfaces/secret-data.interface.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/auth/interfaces/secret-data.interface.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/interfaces/secret-data.interface.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/interfaces/secret-data.interface.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">export</span> <span class="kr">interface</span> <span class="nx">SecretData</span> <span class="p">{</span>
<span class="nl">jwtAccessToken</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="nl">jwtRefreshToken</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="nl">refreshTokenId</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<h3 id="authentication-guards">Authentication Guards</h3>
<p>We will also be implementing <a href="https://docs.nestjs.com/guards">guards</a>, which are special classes that Nest.js uses to limit access to endpoints/routes in our application. They are the recommended method for performing authorization. I have created a jwt-auth guard, which we will be using later on to limit access to certain endpoints to logged in users, and a refresh-auth guard, which can only be accessed via a valid refresh token. Both the guards extend the AuthGuard parent class that comes with Nest.js.</p>
<p>Jwt Auth Guard:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/guards/jwt-auth.guard.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/auth/guards/jwt-auth.guard.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/guards/jwt-auth.guard.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/guards/jwt-auth.guard.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Injectable</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AuthGuard</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/passport</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Injectable</span><span class="p">()</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">JwtAuthGuard</span> <span class="kd">extends</span> <span class="nx">AuthGuard</span><span class="p">(</span><span class="dl">'</span><span class="s1">jwt</span><span class="dl">'</span><span class="p">)</span> <span class="p">{}</span></code></pre></figure>
<p>Refresh Guard:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/guards/refresh-auth.guard.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/auth/guards/refresh-auth.guard.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/guards/refresh-auth.guard.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/guards/refresh-auth.guard.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Injectable</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AuthGuard</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/passport</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Injectable</span><span class="p">()</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">RefreshGuard</span> <span class="kd">extends</span> <span class="nx">AuthGuard</span><span class="p">(</span><span class="dl">'</span><span class="s1">refresh</span><span class="dl">'</span><span class="p">)</span> <span class="p">{}</span></code></pre></figure>
<h3 id="auth-service">Auth Service</h3>
<p>We will be implementing most of the actual authentication logic in the auth service. Since we will be using redis as a temporary cache for our refresh tokens, where they will be automatically exipred after one day, we will need to install te <em>nestjs-redis</em> node module so as to interface with redis:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm <span class="nb">install </span>nestjs-redis <span class="nt">--save</span>
</code></pre></div></div>
<p>Now, onto implementing the auth service. Here is is:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/auth.service.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/auth/auth.service.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/auth.service.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/auth.service.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span>
<span class="nx">BadRequestException</span><span class="p">,</span>
<span class="nx">Injectable</span><span class="p">,</span>
<span class="nx">InternalServerErrorException</span><span class="p">,</span>
<span class="nx">Logger</span><span class="p">,</span>
<span class="nx">NotFoundException</span><span class="p">,</span>
<span class="nx">UnprocessableEntityException</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CustomersService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../customers/customers.service</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CreateCustomerDto</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../customers/dto/create-customer.dto</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">bcrypt</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">bcrypt</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">PostgresErrorCode</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../common/enums/postgres-error-codes.enum</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Customer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../customers/entities/customer.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">JwtTokenPayload</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./interfaces/jwt-payload.interface</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">JwtService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/jwt</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">util</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">util</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">RedisService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">nestjs-redis</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Redis</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ioredis</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ConfigService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/config</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">fs</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">fs</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">RedisKeys</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../common/enums/redis-keys.enum</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Injectable</span><span class="p">()</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">AuthService</span> <span class="p">{</span>
<span class="nl">redisClient</span><span class="p">:</span> <span class="nx">Redis</span><span class="p">;</span>
<span class="nl">payload</span><span class="p">:</span> <span class="nx">JwtTokenPayload</span><span class="p">;</span>
<span class="kd">constructor</span><span class="p">(</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="nx">customersService</span><span class="p">:</span> <span class="nx">CustomersService</span><span class="p">,</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="nx">jwtService</span><span class="p">:</span> <span class="nx">JwtService</span><span class="p">,</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="nx">redisService</span><span class="p">:</span> <span class="nx">RedisService</span><span class="p">,</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="nx">configService</span><span class="p">:</span> <span class="nx">ConfigService</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">redisClient</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">redisService</span><span class="p">.</span><span class="nx">getClient</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">async</span> <span class="nx">register</span><span class="p">(</span><span class="nx">registrationData</span><span class="p">:</span> <span class="nx">CreateCustomerDto</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Customer</span><span class="o">></span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">registrationData</span><span class="p">.</span><span class="nx">password</span> <span class="o">!==</span> <span class="nx">registrationData</span><span class="p">.</span><span class="nx">confirmPassword</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">UnprocessableEntityException</span><span class="p">(</span><span class="dl">'</span><span class="s1">Passwords are not matching.</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">salt</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">bcrypt</span><span class="p">.</span><span class="nx">genSalt</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">hashedPassword</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">bcrypt</span><span class="p">.</span><span class="nx">hash</span><span class="p">(</span><span class="nx">registrationData</span><span class="p">.</span><span class="nx">password</span><span class="p">,</span> <span class="nx">salt</span><span class="p">);</span>
<span class="k">try</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">customersService</span><span class="p">.</span><span class="nx">create</span><span class="p">({</span>
<span class="p">...</span><span class="nx">registrationData</span><span class="p">,</span>
<span class="na">password</span><span class="p">:</span> <span class="nx">hashedPassword</span><span class="p">,</span>
<span class="p">});</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">error</span><span class="p">?.</span><span class="nx">code</span> <span class="o">===</span> <span class="nx">PostgresErrorCode</span><span class="p">.</span><span class="nx">UniqueViolation</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">BadRequestException</span><span class="p">(</span>
<span class="dl">'</span><span class="s1">Customer with that email already exists</span><span class="dl">'</span><span class="p">,</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="nx">Logger</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">[authService] registration error</span><span class="dl">'</span><span class="p">,</span> <span class="nx">util</span><span class="p">.</span><span class="nx">inspect</span><span class="p">(</span><span class="nx">error</span><span class="p">));</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">InternalServerErrorException</span><span class="p">(</span>
<span class="dl">'</span><span class="s1">Something went wrong during registration</span><span class="dl">'</span><span class="p">,</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">async</span> <span class="nx">getAuthenticatedCustomer</span><span class="p">(</span>
<span class="nx">email</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="nx">plainTextPassword</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Customer</span><span class="o">></span> <span class="p">{</span>
<span class="k">try</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">customer</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">customersService</span><span class="p">.</span><span class="nx">getByEmail</span><span class="p">(</span><span class="nx">email</span><span class="p">);</span>
<span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">verifyPassword</span><span class="p">(</span><span class="nx">plainTextPassword</span><span class="p">,</span> <span class="nx">customer</span><span class="p">.</span><span class="nx">password</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">customer</span><span class="p">;</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">BadRequestException</span><span class="p">(</span><span class="dl">'</span><span class="s1">Wrong credentials provided</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">async</span> <span class="nx">verifyPassword</span><span class="p">(</span>
<span class="nx">plainTextPassword</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="nx">hashedPassword</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">isPasswordMatching</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">bcrypt</span><span class="p">.</span><span class="nx">compare</span><span class="p">(</span>
<span class="nx">plainTextPassword</span><span class="p">,</span>
<span class="nx">hashedPassword</span><span class="p">,</span>
<span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">isPasswordMatching</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">BadRequestException</span><span class="p">(</span><span class="dl">'</span><span class="s1">Wrong credentials provided</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">async</span> <span class="nx">createJwtAccessToken</span><span class="p">(</span><span class="nx">customer</span><span class="p">:</span> <span class="nx">Customer</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="kr">string</span><span class="o">></span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">payload</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">customerId</span><span class="p">:</span> <span class="nx">customer</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
<span class="p">};</span>
<span class="k">return</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">jwtService</span><span class="p">.</span><span class="nx">signAsync</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">payload</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">async</span> <span class="nx">createJwtRefreshToken</span><span class="p">(</span>
<span class="nx">customerId</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="nx">refreshTokenId</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="kr">string</span><span class="o">></span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">payload</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">customerId</span><span class="p">:</span> <span class="nx">customerId</span><span class="p">,</span>
<span class="p">};</span>
<span class="kd">const</span> <span class="nx">refreshToken</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">jwtService</span><span class="p">.</span><span class="nx">signAsync</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">payload</span><span class="p">,</span> <span class="p">{</span>
<span class="na">privateKey</span><span class="p">:</span> <span class="nx">fs</span>
<span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span>
<span class="k">this</span><span class="p">.</span><span class="nx">configService</span><span class="p">.</span><span class="kd">get</span><span class="o"><</span><span class="kr">string</span><span class="o">></span><span class="p">(</span><span class="dl">'</span><span class="s1">JWT_REFRESH_TOKEN_PRIVATE_KEY</span><span class="dl">'</span><span class="p">),</span>
<span class="p">)</span>
<span class="p">.</span><span class="nx">toString</span><span class="p">(),</span>
<span class="na">expiresIn</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">configService</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">JWT_REFRESH_TOKEN_EXPIRATION_TIME</span><span class="dl">'</span><span class="p">),</span>
<span class="p">});</span>
<span class="c1">// Encrypt the refresh token before storing it in redis</span>
<span class="kd">const</span> <span class="nx">salt</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">bcrypt</span><span class="p">.</span><span class="nx">genSalt</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">hashedRefreshToken</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">bcrypt</span><span class="p">.</span><span class="nx">hash</span><span class="p">(</span><span class="nx">refreshToken</span><span class="p">,</span> <span class="nx">salt</span><span class="p">);</span>
<span class="c1">// Save the hashed access token in redis and set it to expire after a day</span>
<span class="kd">const</span> <span class="nx">redisResponse</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">redisClient</span><span class="p">.</span><span class="nx">setex</span><span class="p">(</span>
<span class="s2">`</span><span class="p">${</span><span class="nx">RedisKeys</span><span class="p">.</span><span class="nx">RefreshToken</span><span class="p">}</span><span class="s2">:</span><span class="p">${</span><span class="nx">customerId</span><span class="p">}</span><span class="s2">:</span><span class="p">${</span><span class="nx">refreshTokenId</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
<span class="mi">86400</span><span class="p">,</span>
<span class="nx">hashedRefreshToken</span><span class="p">,</span>
<span class="p">);</span>
<span class="c1">// Return the unencrypted refresh token back to the customer</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">redisResponse</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">OK</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">refreshToken</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">async</span> <span class="nx">validateJwtRefreshToken</span><span class="p">(</span>
<span class="nx">customerId</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="nx">refreshToken</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="nx">refreshTokenId</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Customer</span><span class="o">></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">customer</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">customersService</span><span class="p">.</span><span class="nx">getById</span><span class="p">(</span><span class="nx">customerId</span><span class="p">);</span>
<span class="c1">// Fetch the encrypted refresh token from redis</span>
<span class="kd">const</span> <span class="nx">savedRefreshToken</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">redisClient</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span>
<span class="s2">`</span><span class="p">${</span><span class="nx">RedisKeys</span><span class="p">.</span><span class="nx">RefreshToken</span><span class="p">}</span><span class="s2">:</span><span class="p">${</span><span class="nx">customer</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">:</span><span class="p">${</span><span class="nx">refreshTokenId</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
<span class="p">);</span>
<span class="c1">// Compare the received refresh token and what is stored in redis</span>
<span class="kd">const</span> <span class="nx">isRefreshTokenMatching</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">bcrypt</span><span class="p">.</span><span class="nx">compare</span><span class="p">(</span>
<span class="nx">refreshToken</span><span class="p">,</span>
<span class="nx">savedRefreshToken</span><span class="p">,</span>
<span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">isRefreshTokenMatching</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">BadRequestException</span><span class="p">(</span><span class="dl">'</span><span class="s1">The refresh tokens do not match</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">savedRefreshToken</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">customer</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">NotFoundException</span><span class="p">(</span><span class="dl">'</span><span class="s1">The refresh token was not found</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">async</span> <span class="nx">removeJwtRefreshToken</span><span class="p">(</span>
<span class="nx">customerId</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="nx">refreshTokenId</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Customer</span><span class="o">></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">customer</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">customersService</span><span class="p">.</span><span class="nx">getById</span><span class="p">(</span><span class="nx">customerId</span><span class="p">);</span>
<span class="c1">// Delete the encrypted refresh token from redis</span>
<span class="kd">const</span> <span class="nx">deletedResult</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">redisClient</span><span class="p">.</span><span class="nx">del</span><span class="p">(</span>
<span class="s2">`</span><span class="p">${</span><span class="nx">RedisKeys</span><span class="p">.</span><span class="nx">RefreshToken</span><span class="p">}</span><span class="s2">:</span><span class="p">${</span><span class="nx">customer</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">:</span><span class="p">${</span><span class="nx">refreshTokenId</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
<span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">deletedResult</span> <span class="o">===</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">customer</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">NotFoundException</span><span class="p">(</span><span class="dl">'</span><span class="s1">The refresh token was not found</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>The <em>register</em> method handles customer registration by first doing a password validation, then uses the <em>bcrypt</em> library to encrypt the customers password just before saving it together with all the other customer details. Since we have enforced a uniqueness constraint on our customer table, we want the method to throw a bad request exception if a customer attempts to register twice with the same email.</p>
<p>Customer logins are handled by the <em>getAuthenticatedCustomer</em> method, which calls the verifyPassword method to verify if the customer’s supplied password matches what is saved in the database, as well as the getByEmail method in the customers service that checks if a customer’s supplied email matches what is saved in the database. The <em>createJwtAccessToken</em> method creates the customer’s access token that an authenticated customer uses to access protected routes. The <em>createJwtRefreshToken</em> method creates the refresh token that is used to fetch a new access token once the previous one in use expires. Using the redis client, we explicity tell redis to store the refresh token for 86,400 seconds or 24 hours once it has been created an returned to the customer via a cookie. For security reasons, we encrypt the generated refresh tokens using bcrypt before saving them on redis, to prevent their misuse by unauthorized users, and also since refresh tokens are private data only meant for the customer.</p>
<p>We use the <em>validateJwtRefreshToken</em> method to validate the refresh token received from the cookie. To do so, we must first validate the customer’s ID, which we also fetch from the cookie, then if the customer exists on our backend, we reconstruct a redis key denoted by string: <code class="language-plaintext highlighter-rouge">${RedisKeys.RefreshToken}:${customer.id}:${refreshTokenId}</code>. We use this key to fetch the encrypted access token, then use bcrypt once again to compare the encrypted refresh token with the refresh token that has been received from the cookie. At this point, if the tokens don’t match we throw a bad request exception (http 400 error), otherwise we return a customer object.</p>
<p>Lastly, the <em>removeJwtRefreshToken</em> removes the refresh token from redis, and is called as soon as a customer logs out from our application.</p>
<p>I have also included an enum for storing the names of redis keys that the auth service will be using:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/common/enums/redis-keys.enum.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/common/enums/redis-keys.enum.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/common/enums/redis-keys.enum.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/common/enums/redis-keys.enum.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">export</span> <span class="kr">enum</span> <span class="nx">RedisKeys</span> <span class="p">{</span>
<span class="nx">RefreshToken</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">refresh-token</span><span class="dl">'</span><span class="p">,</span>
<span class="p">}</span></code></pre></figure>
<p>And another for storing the postgres error code:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/common/enums/postgres-error-codes.enum.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/common/enums/postgres-error-codes.enum.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/common/enums/postgres-error-codes.enum.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/common/enums/postgres-error-codes.enum.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">export</span> <span class="kr">enum</span> <span class="nx">PostgresErrorCode</span> <span class="p">{</span>
<span class="nx">UniqueViolation</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">23505</span><span class="dl">'</span><span class="p">,</span>
<span class="p">}</span></code></pre></figure>
<h3 id="auth-controller">Auth Controller</h3>
<p>Here is our auth controller with the <em>register</em>, <em>login</em>, <em>logOut</em> and <em>refreshToken</em> methods, that call the services that we defined above.</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/auth.controller.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/auth/auth.controller.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/auth.controller.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/auth.controller.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span>
<span class="nx">Body</span><span class="p">,</span>
<span class="nx">Controller</span><span class="p">,</span>
<span class="nx">Delete</span><span class="p">,</span>
<span class="nx">Get</span><span class="p">,</span>
<span class="nx">Post</span><span class="p">,</span>
<span class="nx">Req</span><span class="p">,</span>
<span class="nx">Res</span><span class="p">,</span>
<span class="nx">UseGuards</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AuthService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./auth.service</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Response</span><span class="p">,</span> <span class="nx">Request</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">express</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CreateCustomerDto</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../customers/dto/create-customer.dto</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Customer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../customers/entities/customer.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AuthGuard</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/passport</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">SecretData</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./interfaces/secret-data.interface</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">RefreshGuard</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./guards/refresh-auth.guard</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">uuid</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">uuid</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">JwtAuthGuard</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./guards/jwt-auth.guard</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CookieNames</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../common/enums/cookie-names.enum</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Controller</span><span class="p">(</span><span class="dl">'</span><span class="s1">auth</span><span class="dl">'</span><span class="p">)</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">AuthController</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span><span class="k">private</span> <span class="k">readonly</span> <span class="nx">authService</span><span class="p">:</span> <span class="nx">AuthService</span><span class="p">)</span> <span class="p">{}</span>
<span class="p">@</span><span class="nd">Post</span><span class="p">(</span><span class="dl">'</span><span class="s1">login</span><span class="dl">'</span><span class="p">)</span>
<span class="p">@</span><span class="nd">UseGuards</span><span class="p">(</span><span class="nx">AuthGuard</span><span class="p">(</span><span class="dl">'</span><span class="s1">local</span><span class="dl">'</span><span class="p">))</span>
<span class="k">async</span> <span class="nx">login</span><span class="p">(@</span><span class="nd">Req</span><span class="p">()</span> <span class="nx">req</span><span class="p">:</span> <span class="nx">Request</span><span class="p">,</span> <span class="p">@</span><span class="nd">Res</span><span class="p">({</span> <span class="na">passthrough</span><span class="p">:</span> <span class="kc">true</span> <span class="p">})</span> <span class="nx">res</span><span class="p">:</span> <span class="nx">Response</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">customer</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">user</span> <span class="k">as</span> <span class="nx">Customer</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">refreshTokenId</span> <span class="o">=</span> <span class="nx">uuid</span><span class="p">.</span><span class="nx">v4</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">jwtToken</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">authService</span><span class="p">.</span><span class="nx">createJwtAccessToken</span><span class="p">(</span><span class="nx">customer</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">refreshToken</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">authService</span><span class="p">.</span><span class="nx">createJwtRefreshToken</span><span class="p">(</span>
<span class="nx">customer</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
<span class="nx">refreshTokenId</span><span class="p">,</span>
<span class="p">);</span>
<span class="kd">const</span> <span class="nx">secretData</span><span class="p">:</span> <span class="nx">SecretData</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">jwtAccessToken</span><span class="p">:</span> <span class="nx">jwtToken</span><span class="p">,</span>
<span class="na">jwtRefreshToken</span><span class="p">:</span> <span class="nx">refreshToken</span><span class="p">,</span>
<span class="na">refreshTokenId</span><span class="p">:</span> <span class="nx">refreshTokenId</span><span class="p">,</span>
<span class="p">};</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">cookie</span><span class="p">(</span><span class="nx">CookieNames</span><span class="p">.</span><span class="nx">AuthCookie</span><span class="p">,</span> <span class="nx">secretData</span><span class="p">,</span> <span class="p">{</span>
<span class="na">httpOnly</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">signed</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="p">});</span>
<span class="k">return</span> <span class="p">{</span> <span class="na">msg</span><span class="p">:</span> <span class="dl">'</span><span class="s1">success</span><span class="dl">'</span> <span class="p">};</span>
<span class="p">}</span>
<span class="p">@</span><span class="nd">Get</span><span class="p">(</span><span class="dl">'</span><span class="s1">refresh_token</span><span class="dl">'</span><span class="p">)</span>
<span class="p">@</span><span class="nd">UseGuards</span><span class="p">(</span><span class="nx">RefreshGuard</span><span class="p">)</span>
<span class="k">async</span> <span class="nx">refreshToken</span><span class="p">(</span>
<span class="p">@</span><span class="nd">Req</span><span class="p">()</span> <span class="nx">req</span><span class="p">:</span> <span class="nx">Request</span><span class="p">,</span>
<span class="p">@</span><span class="nd">Res</span><span class="p">({</span> <span class="na">passthrough</span><span class="p">:</span> <span class="kc">true</span> <span class="p">})</span> <span class="nx">res</span><span class="p">:</span> <span class="nx">Response</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">customer</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">user</span> <span class="k">as</span> <span class="nx">Customer</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">jwtToken</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">authService</span><span class="p">.</span><span class="nx">createJwtAccessToken</span><span class="p">(</span><span class="nx">customer</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">refreshTokenId</span> <span class="o">=</span> <span class="nx">uuid</span><span class="p">.</span><span class="nx">v4</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">refreshToken</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">authService</span><span class="p">.</span><span class="nx">createJwtRefreshToken</span><span class="p">(</span>
<span class="nx">customer</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
<span class="nx">refreshTokenId</span><span class="p">,</span>
<span class="p">);</span>
<span class="kd">const</span> <span class="nx">secretData</span><span class="p">:</span> <span class="nx">SecretData</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">jwtAccessToken</span><span class="p">:</span> <span class="nx">jwtToken</span><span class="p">,</span>
<span class="na">jwtRefreshToken</span><span class="p">:</span> <span class="nx">refreshToken</span><span class="p">,</span>
<span class="na">refreshTokenId</span><span class="p">:</span> <span class="nx">refreshTokenId</span><span class="p">,</span>
<span class="p">};</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">cookie</span><span class="p">(</span><span class="nx">CookieNames</span><span class="p">.</span><span class="nx">RefreshCookie</span><span class="p">,</span> <span class="nx">secretData</span><span class="p">,</span> <span class="p">{</span>
<span class="na">httpOnly</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">signed</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="p">});</span>
<span class="k">return</span> <span class="p">{</span> <span class="na">msg</span><span class="p">:</span> <span class="dl">'</span><span class="s1">success</span><span class="dl">'</span> <span class="p">};</span>
<span class="p">}</span>
<span class="p">@</span><span class="nd">Post</span><span class="p">(</span><span class="dl">'</span><span class="s1">register</span><span class="dl">'</span><span class="p">)</span>
<span class="k">async</span> <span class="nx">register</span><span class="p">(</span>
<span class="p">@</span><span class="nd">Body</span><span class="p">()</span> <span class="nx">registrationData</span><span class="p">:</span> <span class="nx">CreateCustomerDto</span><span class="p">,</span>
<span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Customer</span><span class="o">></span> <span class="p">{</span>
<span class="k">return</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">authService</span><span class="p">.</span><span class="nx">register</span><span class="p">(</span><span class="nx">registrationData</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">@</span><span class="nd">Delete</span><span class="p">(</span><span class="dl">'</span><span class="s1">log_out</span><span class="dl">'</span><span class="p">)</span>
<span class="p">@</span><span class="nd">UseGuards</span><span class="p">(</span><span class="nx">JwtAuthGuard</span><span class="p">)</span>
<span class="k">async</span> <span class="nx">logOut</span><span class="p">(@</span><span class="nd">Req</span><span class="p">()</span> <span class="nx">req</span><span class="p">:</span> <span class="nx">Request</span><span class="p">,</span> <span class="p">@</span><span class="nd">Res</span><span class="p">()</span> <span class="nx">res</span><span class="p">:</span> <span class="nx">Response</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">customer</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">user</span> <span class="k">as</span> <span class="nx">Customer</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">tokenData</span><span class="p">:</span> <span class="nx">SecretData</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">signedCookies</span><span class="p">[</span><span class="nx">CookieNames</span><span class="p">.</span><span class="nx">AuthCookie</span><span class="p">];</span>
<span class="c1">// Remove refresh token from redis</span>
<span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">authService</span><span class="p">.</span><span class="nx">removeJwtRefreshToken</span><span class="p">(</span>
<span class="nx">customer</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
<span class="nx">tokenData</span><span class="p">.</span><span class="nx">refreshTokenId</span><span class="p">,</span>
<span class="p">);</span>
<span class="c1">// Delete auth cookie and refresh cookie</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">clearCookie</span><span class="p">(</span><span class="nx">CookieNames</span><span class="p">.</span><span class="nx">AuthCookie</span><span class="p">,</span> <span class="p">{</span> <span class="na">signed</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="na">httpOnly</span><span class="p">:</span> <span class="kc">true</span> <span class="p">});</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">clearCookie</span><span class="p">(</span><span class="nx">CookieNames</span><span class="p">.</span><span class="nx">RefreshCookie</span><span class="p">,</span> <span class="p">{</span>
<span class="na">signed</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">httpOnly</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="p">});</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">send</span><span class="p">({</span> <span class="na">msg</span><span class="p">:</span> <span class="dl">'</span><span class="s1">success</span><span class="dl">'</span> <span class="p">}).</span><span class="nx">end</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>We pass to our <em>login</em> method the decorator:</p>
<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="p">@</span><span class="nd">UseGuards</span><span class="p">(</span><span class="nx">AuthGuard</span><span class="p">(</span><span class="dl">'</span><span class="s1">local</span><span class="dl">'</span><span class="p">))</span>
</code></pre></div></div>
<p>This tells our <em>login</em> method to use our local strategy that we defined earlier, and to restrict our login route via that strategy. Since we want to direct our response object to set cookies, we will need to pass the passthrough parameter like so:</p>
<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">@</span><span class="nd">Res</span><span class="p">({</span> <span class="na">passthrough</span><span class="p">:</span> <span class="kc">true</span> <span class="p">})</span>
</code></pre></div></div>
<p>We are also using the <a href="https://docs.nestjs.com/controllers#routing">library-specific</a> method for handling requests and responses, so don’t forget to import their corresponding libraries from the express framework:</p>
<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">Response</span><span class="p">,</span> <span class="nx">Request</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">express</span><span class="dl">'</span><span class="p">;</span>
</code></pre></div></div>
<p>Next, we fetch the customer details via the request object, and proceed to create both our JWT access and refresh tokens. We store these tokens in a cookie called <em>auth-cookie</em>, which we send back in the response, together with a <em>success</em> message.</p>
<p>The <em>refreshToken</em> method is protected via the refresh guard in the line:</p>
<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="p">@</span><span class="nd">UseGuards</span><span class="p">(</span><span class="nx">RefreshGuard</span><span class="p">)</span>
</code></pre></div></div>
<p>Meaning, only a customer with a valid refresh token can access it. In a similar fashion to the login method, it fetches the customer information via the request object, then generates a fresh JWT access and refresh token which are stored in a cookie named <em>refresh-cookie</em>, which is sent back in the response to the customer along with a <em>success</em> message.</p>
<p>The <em>register</em> method calls the similarly named method in the auth service in order to register our customer, while the <em>logOut</em> method logs out our customer by first removing the JWT refresh token from redis, then deleting the <em>auth-cookie</em> and <em>refresh-cookie</em>.</p>
<p>The cookie names enum:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/common/enums/cookie-names.enum.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/common/enums/cookie-names.enum.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/common/enums/cookie-names.enum.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/common/enums/cookie-names.enum.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">export</span> <span class="kr">enum</span> <span class="nx">CookieNames</span> <span class="p">{</span>
<span class="nx">AuthCookie</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">auth-cookie</span><span class="dl">'</span><span class="p">,</span>
<span class="nx">RefreshCookie</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">refresh-cookie</span><span class="dl">'</span><span class="p">,</span>
<span class="p">}</span></code></pre></figure>
<h3 id="unit-tests">Unit Tests</h3>
<p>As is common in modern software development, it is a good idea to include tests with the functionality that we have just built. These tests have the main purpose of verifying the correctness of our logic, and making it easier to debug issues later on, as well as add new functionality without the worry of introducing breaking changes. Nest.js supports two types of tests: <a href="https://docs.nestjs.com/fundamentals/testing#unit-testing">unit tests</a> and <a href="https://docs.nestjs.com/fundamentals/testing#end-to-end-testing">end to end tests</a>. Unit tests usually test functionality contained within a single class, usually a service or controller class, while end to end tests are for testing the functionality of an entire endpoint. We shall include unit tests for both our controller and service, as shown below. Later on, we will also include end to end tests for the controller as well.</p>
<p>Auth Controller tests:</p>
<p>For now, I have created just a basic initialization check for the controller class, since most of the functionality in the controller class will be tested later on via end to end tests:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/spec/auth.controller.spec.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/auth/spec/auth.controller.spec.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/spec/auth.controller.spec.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/spec/auth.controller.spec.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Test</span><span class="p">,</span> <span class="nx">TestingModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/testing</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AuthController</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../auth.controller</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AuthService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../auth.service</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">AuthController</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">controller</span><span class="p">:</span> <span class="nx">AuthController</span><span class="p">;</span>
<span class="nx">beforeEach</span><span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="na">module</span><span class="p">:</span> <span class="nx">TestingModule</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">Test</span><span class="p">.</span><span class="nx">createTestingModule</span><span class="p">({</span>
<span class="na">controllers</span><span class="p">:</span> <span class="p">[</span><span class="nx">AuthController</span><span class="p">],</span>
<span class="na">providers</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="na">provide</span><span class="p">:</span> <span class="nx">AuthService</span><span class="p">,</span>
<span class="na">useValue</span><span class="p">:</span> <span class="p">{},</span>
<span class="p">},</span>
<span class="p">],</span>
<span class="p">}).</span><span class="nx">compile</span><span class="p">();</span>
<span class="nx">controller</span> <span class="o">=</span> <span class="kr">module</span><span class="p">.</span><span class="kd">get</span><span class="o"><</span><span class="nx">AuthController</span><span class="o">></span><span class="p">(</span><span class="nx">AuthController</span><span class="p">);</span>
<span class="p">});</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should be defined</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">expect</span><span class="p">(</span><span class="nx">controller</span><span class="p">).</span><span class="nx">toBeDefined</span><span class="p">();</span>
<span class="p">});</span>
<span class="p">});</span></code></pre></figure>
<p>Auth Service tests:</p>
<p>In the auth service tests, I have tried as much as possible to mock as many dependencies as I could, so as to prevent the calling of actual resources, as well as to make the tests as light as possible and to execute as fast as possible. Behind the scenes, we’re using <a href="https://github.com/facebook/jest">jest</a> as our testing framework, which comes with Nest.js by default. The <em>describe</em> blocks within the test spec explain pretty much what the given spec is required to do:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/spec/auth.service.spec.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/auth/spec/auth.service.spec.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/spec/auth.service.spec.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/auth/spec/auth.service.spec.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">ConfigService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/config</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">JwtService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/jwt</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Test</span><span class="p">,</span> <span class="nx">TestingModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/testing</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">getRepositoryToken</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">RedisService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">nestjs-redis</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CustomersService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../customers/customers.service</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Customer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../customers/entities/customer.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AuthService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../auth.service</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">JwtTokenPayload</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../interfaces/jwt-payload.interface</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">bcrypt</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">bcrypt</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CreateCustomerDto</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../customers/dto/create-customer.dto</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">AuthService</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">service</span><span class="p">:</span> <span class="nx">AuthService</span><span class="p">;</span>
<span class="kd">let</span> <span class="na">customerService</span><span class="p">:</span> <span class="nx">CustomersService</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">fakeCustomer</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Customer</span><span class="p">({</span>
<span class="na">id</span><span class="p">:</span> <span class="dl">'</span><span class="s1">2173dd22-42ed-4091-bb46-aaca401efa45</span><span class="dl">'</span><span class="p">,</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Test Customer</span><span class="dl">'</span><span class="p">,</span>
<span class="na">email</span><span class="p">:</span> <span class="dl">'</span><span class="s1">testcustomer@example.com</span><span class="dl">'</span><span class="p">,</span>
<span class="na">phoneNumber</span><span class="p">:</span> <span class="dl">'</span><span class="s1">0720123456</span><span class="dl">'</span><span class="p">,</span>
<span class="na">password</span><span class="p">:</span> <span class="dl">'</span><span class="s1">strongPassword</span><span class="dl">'</span><span class="p">,</span>
<span class="p">});</span>
<span class="kd">let</span> <span class="na">bcryptCompare</span><span class="p">:</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">Mock</span><span class="p">;</span>
<span class="kd">let</span> <span class="na">customerData</span><span class="p">:</span> <span class="nx">Customer</span><span class="p">;</span>
<span class="kd">let</span> <span class="na">findCustomer</span><span class="p">:</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">Mock</span><span class="p">;</span>
<span class="nx">beforeEach</span><span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">getClient</span> <span class="o">=</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">((</span><span class="na">data</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">data</span><span class="p">;</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">signAsync</span> <span class="o">=</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">((</span><span class="na">data</span><span class="p">:</span> <span class="nx">JwtTokenPayload</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">data</span><span class="p">.</span><span class="nx">customerId</span><span class="p">;</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="kd">get</span> <span class="o">=</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">mockRedisService</span> <span class="o">=</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">().</span><span class="nx">mockImplementation</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">getClient</span><span class="p">:</span> <span class="nx">getClient</span><span class="p">,</span>
<span class="p">};</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">mockJwtService</span> <span class="o">=</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">().</span><span class="nx">mockImplementation</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">signAsync</span><span class="p">:</span> <span class="nx">signAsync</span><span class="p">,</span>
<span class="p">};</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">mockConfigService</span> <span class="o">=</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">().</span><span class="nx">mockImplementation</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">get</span><span class="p">:</span> <span class="kd">get</span><span class="p">,</span>
<span class="p">};</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="na">module</span><span class="p">:</span> <span class="nx">TestingModule</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">Test</span><span class="p">.</span><span class="nx">createTestingModule</span><span class="p">({</span>
<span class="na">providers</span><span class="p">:</span> <span class="p">[</span>
<span class="nx">AuthService</span><span class="p">,</span>
<span class="nx">CustomersService</span><span class="p">,</span>
<span class="p">{</span>
<span class="na">provide</span><span class="p">:</span> <span class="nx">JwtService</span><span class="p">,</span>
<span class="na">useClass</span><span class="p">:</span> <span class="nx">mockJwtService</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">provide</span><span class="p">:</span> <span class="nx">RedisService</span><span class="p">,</span>
<span class="na">useClass</span><span class="p">:</span> <span class="nx">mockRedisService</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">provide</span><span class="p">:</span> <span class="nx">ConfigService</span><span class="p">,</span>
<span class="na">useClass</span><span class="p">:</span> <span class="nx">mockConfigService</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">provide</span><span class="p">:</span> <span class="nx">getRepositoryToken</span><span class="p">(</span><span class="nx">Customer</span><span class="p">),</span>
<span class="na">useValue</span><span class="p">:</span> <span class="p">{},</span>
<span class="p">},</span>
<span class="p">],</span>
<span class="p">}).</span><span class="nx">compile</span><span class="p">();</span>
<span class="nx">service</span> <span class="o">=</span> <span class="kr">module</span><span class="p">.</span><span class="kd">get</span><span class="o"><</span><span class="nx">AuthService</span><span class="o">></span><span class="p">(</span><span class="nx">AuthService</span><span class="p">);</span>
<span class="nx">customerService</span> <span class="o">=</span> <span class="kr">module</span><span class="p">.</span><span class="kd">get</span><span class="o"><</span><span class="nx">CustomersService</span><span class="o">></span><span class="p">(</span><span class="nx">CustomersService</span><span class="p">);</span>
<span class="nx">bcryptCompare</span> <span class="o">=</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">().</span><span class="nx">mockReturnValue</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span>
<span class="p">(</span><span class="nx">bcrypt</span><span class="p">.</span><span class="nx">compare</span> <span class="k">as</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">Mock</span><span class="p">)</span> <span class="o">=</span> <span class="nx">bcryptCompare</span><span class="p">;</span>
<span class="nx">customerData</span> <span class="o">=</span> <span class="p">{</span> <span class="p">...</span><span class="nx">fakeCustomer</span> <span class="p">};</span>
<span class="nx">findCustomer</span> <span class="o">=</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">().</span><span class="nx">mockResolvedValue</span><span class="p">(</span><span class="nx">customerData</span><span class="p">);</span>
<span class="p">});</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should be defined</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">expect</span><span class="p">(</span><span class="nx">service</span><span class="p">).</span><span class="nx">toBeDefined</span><span class="p">();</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">register</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should return the customer details</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">customer</span><span class="p">:</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Customer</span><span class="o">></span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">registrationData</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">CreateCustomerDto</span><span class="p">();</span>
<span class="nx">jest</span><span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">service</span><span class="p">,</span> <span class="dl">'</span><span class="s1">register</span><span class="dl">'</span><span class="p">).</span><span class="nx">mockImplementation</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">customer</span><span class="p">);</span>
<span class="nx">expect</span><span class="p">(</span><span class="k">await</span> <span class="nx">service</span><span class="p">.</span><span class="nx">register</span><span class="p">(</span><span class="nx">registrationData</span><span class="p">)).</span><span class="nx">toBe</span><span class="p">(</span><span class="nx">customer</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">when creating a jwt access token</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should return a string</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">expect</span><span class="p">(</span><span class="k">typeof</span> <span class="p">(</span><span class="k">await</span> <span class="nx">service</span><span class="p">.</span><span class="nx">createJwtAccessToken</span><span class="p">(</span><span class="nx">fakeCustomer</span><span class="p">))).</span><span class="nx">toEqual</span><span class="p">(</span>
<span class="dl">'</span><span class="s1">string</span><span class="dl">'</span><span class="p">,</span>
<span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">when creating a jwt refresh token</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should return a string</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">result</span><span class="p">:</span> <span class="nb">Promise</span><span class="o"><</span><span class="kr">string</span><span class="o">></span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">testRefreshTokenId</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">2173dd22-42ed-4091-bb46-aaca401efa46</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">jest</span>
<span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">service</span><span class="p">,</span> <span class="dl">'</span><span class="s1">createJwtRefreshToken</span><span class="dl">'</span><span class="p">)</span>
<span class="p">.</span><span class="nx">mockImplementation</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">result</span><span class="p">);</span>
<span class="nx">expect</span><span class="p">(</span>
<span class="k">await</span> <span class="nx">service</span><span class="p">.</span><span class="nx">createJwtRefreshToken</span><span class="p">(</span>
<span class="nx">fakeCustomer</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
<span class="nx">testRefreshTokenId</span><span class="p">,</span>
<span class="p">),</span>
<span class="p">).</span><span class="nx">toBe</span><span class="p">(</span><span class="nx">result</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">when validating a jwt refresh token</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should return the customer details</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">result</span><span class="p">:</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Customer</span><span class="o">></span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">testRefreshToken</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">testrefreshtoken</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">testRefreshTokenId</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">2173dd22-42ed-4091-bb46-aaca401efa46</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">jest</span>
<span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">service</span><span class="p">,</span> <span class="dl">'</span><span class="s1">validateJwtRefreshToken</span><span class="dl">'</span><span class="p">)</span>
<span class="p">.</span><span class="nx">mockImplementation</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">result</span><span class="p">);</span>
<span class="nx">expect</span><span class="p">(</span>
<span class="k">await</span> <span class="nx">service</span><span class="p">.</span><span class="nx">validateJwtRefreshToken</span><span class="p">(</span>
<span class="nx">fakeCustomer</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
<span class="nx">testRefreshToken</span><span class="p">,</span>
<span class="nx">testRefreshTokenId</span><span class="p">,</span>
<span class="p">),</span>
<span class="p">).</span><span class="nx">toBe</span><span class="p">(</span><span class="nx">result</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">when deleting a jwt refresh token</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should return the customer details</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">result</span><span class="p">:</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Customer</span><span class="o">></span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">testRefreshTokenId</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">2173dd22-42ed-4091-bb46-aaca401efa46</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">jest</span>
<span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">service</span><span class="p">,</span> <span class="dl">'</span><span class="s1">removeJwtRefreshToken</span><span class="dl">'</span><span class="p">)</span>
<span class="p">.</span><span class="nx">mockImplementation</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">result</span><span class="p">);</span>
<span class="nx">expect</span><span class="p">(</span>
<span class="k">await</span> <span class="nx">service</span><span class="p">.</span><span class="nx">removeJwtRefreshToken</span><span class="p">(</span>
<span class="nx">fakeCustomer</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
<span class="nx">testRefreshTokenId</span><span class="p">,</span>
<span class="p">),</span>
<span class="p">).</span><span class="nx">toBe</span><span class="p">(</span><span class="nx">result</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">when accessing the data of an authenticating customer</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">and the provided password is not valid</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">beforeEach</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">bcryptCompare</span><span class="p">.</span><span class="nx">mockReturnValue</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
<span class="p">});</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should throw an error</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">expect</span><span class="p">(</span>
<span class="nx">service</span><span class="p">.</span><span class="nx">getAuthenticatedCustomer</span><span class="p">(</span>
<span class="nx">fakeCustomer</span><span class="p">.</span><span class="nx">email</span><span class="p">,</span>
<span class="nx">fakeCustomer</span><span class="p">.</span><span class="nx">password</span><span class="p">,</span>
<span class="p">),</span>
<span class="p">).</span><span class="nx">rejects</span><span class="p">.</span><span class="nx">toThrow</span><span class="p">();</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">and the provided password is valid</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">beforeEach</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">bcryptCompare</span><span class="p">.</span><span class="nx">mockReturnValue</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">and the customer is found in the database</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">beforeEach</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">findCustomer</span><span class="p">.</span><span class="nx">mockReturnValue</span><span class="p">(</span><span class="nx">customerData</span><span class="p">);</span>
<span class="p">});</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should return the customer data</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">try</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">customer</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">service</span><span class="p">.</span><span class="nx">getAuthenticatedCustomer</span><span class="p">(</span>
<span class="nx">fakeCustomer</span><span class="p">.</span><span class="nx">email</span><span class="p">,</span>
<span class="nx">fakeCustomer</span><span class="p">.</span><span class="nx">password</span><span class="p">,</span>
<span class="p">);</span>
<span class="nx">expect</span><span class="p">(</span><span class="nx">customer</span><span class="p">).</span><span class="nx">toBe</span><span class="p">(</span><span class="nx">customerData</span><span class="p">);</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">[authService spec] error</span><span class="dl">'</span><span class="p">,</span> <span class="nx">error</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">and the customer is not found in the database</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">beforeEach</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">findCustomer</span><span class="p">.</span><span class="nx">mockResolvedValue</span><span class="p">(</span><span class="kc">undefined</span><span class="p">);</span>
<span class="p">});</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should throw an error</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">expect</span><span class="p">(</span>
<span class="nx">service</span><span class="p">.</span><span class="nx">getAuthenticatedCustomer</span><span class="p">(</span>
<span class="nx">fakeCustomer</span><span class="p">.</span><span class="nx">email</span><span class="p">,</span>
<span class="nx">fakeCustomer</span><span class="p">.</span><span class="nx">password</span><span class="p">,</span>
<span class="p">),</span>
<span class="p">).</span><span class="nx">rejects</span><span class="p">.</span><span class="nx">toThrow</span><span class="p">();</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should attempt to get the customer by email</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">getByEmailSpy</span> <span class="o">=</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">customerService</span><span class="p">,</span> <span class="dl">'</span><span class="s1">getByEmail</span><span class="dl">'</span><span class="p">);</span>
<span class="k">try</span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">service</span><span class="p">.</span><span class="nx">getAuthenticatedCustomer</span><span class="p">(</span>
<span class="dl">'</span><span class="s1">user@email.com</span><span class="dl">'</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">strongPassword</span><span class="dl">'</span><span class="p">,</span>
<span class="p">);</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">expect</span><span class="p">(</span><span class="nx">getByEmailSpy</span><span class="p">).</span><span class="nx">toBeCalledTimes</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p">});</span></code></pre></figure>
<p>Here’s how the tests should look like when you run them:</p>
<p>Auth Controller:</p>
<p><img src="/assets/storefront/09-auth-controller-test.png" alt="Auth Controller Test" /></p>
<p>Since it’s only one test case, you should see that only one test has run and passed.</p>
<p>Auth Service:</p>
<p><img src="/assets/storefront/10-auth-service-test.png" alt="Auth Service Test" /></p>
<p>In total, the console should indicate that 10 tests have run and passed. In one of the authentication specs, I have added a <em>console.log</em> line to log the exception thrown when wrong credentials are supplied, but in your case adding this line can be optional.</p>
<h3 id="postman-tests">Postman Tests</h3>
<p>For completeness, we can proceed to do actual tests using a REST client such as <a href="https://www.postman.com/product/rest-client/">Postman</a>. You can use any REST client you prefer, but I’ve chosen Postman since it’s the one that I have the most experience in. In case you’re new to using Postman, I’d recommend creating a collection within Postman by first clicking: <em>File > New</em>. This will take you to the <em>Create New screen</em>. Under the <em>Create New screen</em>, choose <strong>Collection</strong>. Click on the newly created collection, then give it a more memorable name (if you want). Within this collection, click the <strong>Variables</strong> tab, then add a new variable called <strong>api</strong> with both an initial and current value of <em><a href="http://localhost:3000">http://localhost:3000</a></em> like so:</p>
<p><img src="/assets/storefront/13-postman-collection-2.png" alt="Postman Collection" /></p>
<p>Click <strong>Persist All</strong>.</p>
<p>You can then proceed to create new requests within this collection by right clicking it then selecting <strong>Add Request</strong>.</p>
<p>Here are how my tests look like. Kindly observe the contents of both the request and response bodies:</p>
<p>Registration:</p>
<p><img src="/assets/storefront/11-registration-01.png" alt="Registration 01" /></p>
<p>Registering a customer with a duplicate email/phone number:</p>
<p><img src="/assets/storefront/12-registration-02.png" alt="Registration 02" /></p>
<p>In order to test the entire authentication flow via Postman, we start by logging in using our customer credentials:</p>
<p><img src="/assets/storefront/14-login.png" alt="Login 01" /></p>
<p>Click on Cookies to the right of the Postman window. You should see the following screen:</p>
<p><img src="/assets/storefront/15-login-02.png" alt="Login 02" /></p>
<p>An <em>auth-cookie</em> has been sent to Postman after logging in. Click on <em>auth-cookie</em>. You should see the following screen:</p>
<p><img src="/assets/storefront/16-login-03.png" alt="Login 03" /></p>
<p>Click <strong>Save</strong>. The cookie should now be saved within the client, and will be sent back to the server with every subsequent request we make. You should now see the following:</p>
<p><img src="/assets/storefront/17-login-04.png" alt="Login 04" /></p>
<p>You can now proceed to close the <em>Cookies</em> window.</p>
<p>Here are the tests for our refresh token endpoint. Make sure you have first logged in as a customer with valid credentials:</p>
<p><img src="/assets/storefront/18-refresh-token-01.png" alt="Refresh Token 01" /></p>
<p>If you click on the <strong>Cookies</strong> tab at the bottom, you should see that two cookies have been returned in the response: <em>auth-cookie</em> and <em>refresh-cookie</em>. Let us proceed to test if our <em>refresh-cookie</em> works well.</p>
<p><img src="/assets/storefront/19-refresh-token-02.png" alt="Refresh Token 02" /></p>
<p>Click on the Cookies link to the right of Postman and you should see the screen below. The Manage Cookies dialog should now appear.</p>
<p><img src="/assets/storefront/20-refresh-token-03.png" alt="Refresh Token 03" /></p>
<p>Click on the <em>refresh-cookie</em> that we received, then click <strong>Save</strong>.</p>
<p><img src="/assets/storefront/21-refresh-token-04.png" alt="Refresh Token 04" /></p>
<p>The <em>refresh-cookie</em> should now be saved on Postman as shown below:</p>
<p><img src="/assets/storefront/22-refresh-token-5.png" alt="Refresh Token 05" /></p>
<p>Finally, let us test if our log out endpoint is sound. We’ll start by navigating to our logout endpoint via postman and submitting the request - note, it’s a <strong>DELETE</strong> request:</p>
<p><img src="/assets/storefront/23-logout-01.png" alt="Log out 01" /></p>
<p>We should receive a success message in the response body. If we click the Cookies link to the right of the screen, we should note that all the cookies that were previously saved on Postman have now been deleted:</p>
<p><img src="/assets/storefront/24-logout-02.png" alt="Log out 02" /></p>
<p>We’re now done with our Postman tests. Kindly comment on any feedback that you might have.</p>
<h3 id="references">References</h3>
<ul>
<li>Authentication flow sequence diagrams:
<ul>
<li><a href="https://images.app.goo.gl/WY6vtHZfaPH4ZMZ97">https://images.app.goo.gl/WY6vtHZfaPH4ZMZ97</a></li>
<li><a href="https://images.app.goo.gl/Zkv1WxTCa6akstLU9">https://images.app.goo.gl/Zkv1WxTCa6akstLU9</a></li>
</ul>
</li>
<li>Nest.js Documentation: <a href="https://docs.nestjs.com">https://docs.nestjs.com</a></li>
</ul>Isaiah MucheneContinuing with the series, in this iteration of the tutorial we shall be focusing on implementing a way to keep track of our customers, as well as some basic security for our application. For starters, we shall add functionality for registering customers, logging them in as well as logging them out.Create a Customer Module in Nest.js2022-07-22T17:00:00+03:002022-07-22T17:00:00+03:00/2022/07/customer-module<div class="card seriesNote">
<p class="text-center">
This article is <strong>Part 5</strong> in a <strong>10 Part</strong> Series.
</p>
<ul class="list-group list-group-flush">
<li class="list-group-item"> Part 1 -
<a href="/2022/04/bootstrapping-nestjs-ubuntu">Bootstrapping a Nest.js Project on Ubuntu</a>
</li>
<li class="list-group-item"> Part 2 -
<a href="/2022/04/create-nestjs-migrations">Create Nest.js Migrations</a>
</li>
<li class="list-group-item"> Part 3 -
<a href="/2022/04/create-nestjs-crud-resources">Create Nest.js CRUD Resources</a>
</li>
<li class="list-group-item"> Part 4 -
<a href="/2022/06/create-nestjs-seeders">Create Nest.js Seeders</a>
</li>
<li class="list-group-item"> Part 5 -
This Article
</li>
<li class="list-group-item"> Part 6 -
<a href="/2022/07/auth-module">Nest.js Authentication</a>
</li>
<li class="list-group-item"> Part 7 -
<a href="/2022/07/products-module">Create a Products Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 8 -
<a href="/2022/07/stripe-module">Create a Stripe Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 9 -
<a href="/2022/08/orders-module">Create an Orders Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 10 -
<a href="/2022/08/end-to-end-tests">End to End Tests for our Storefront Backend</a>
</li>
</ul>
</div>
<p><br /></p>
<p>In this iteration of the tutorial, we shall be creating the customer module for our backend application. In case of any issues, you can refer to my Github repository <a href="https://github.com/imuchene/storefront-backend">here</a>.</p>
<h3 id="customers-module">Customers Module</h3>
<p>Here’s the customers module file:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/customers/customers.module.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/customers/customers.module.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/customers/customers.module.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/customers/customers.module.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Module</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CustomersService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./customers.service</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">TypeOrmModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Customer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./entities/customer.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Module</span><span class="p">({</span>
<span class="na">imports</span><span class="p">:</span> <span class="p">[</span><span class="nx">TypeOrmModule</span><span class="p">.</span><span class="nx">forFeature</span><span class="p">([</span><span class="nx">Customer</span><span class="p">])],</span>
<span class="na">providers</span><span class="p">:</span> <span class="p">[</span><span class="nx">CustomersService</span><span class="p">],</span>
<span class="na">exports</span><span class="p">:</span> <span class="p">[</span><span class="nx">CustomersService</span><span class="p">],</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">CustomersModule</span> <span class="p">{}</span></code></pre></figure>
<h3 id="customers-service">Customers Service</h3>
<p>Here’s the customers service file:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/customers/customers.service.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/customers/customers.service.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/customers/customers.service.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/customers/customers.service.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Injectable</span><span class="p">,</span> <span class="nx">Logger</span><span class="p">,</span> <span class="nx">NotFoundException</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">InjectRepository</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Repository</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CreateCustomerDto</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./dto/create-customer.dto</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Customer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./entities/customer.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">util</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">util</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Injectable</span><span class="p">()</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">CustomersService</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span>
<span class="p">@</span><span class="nd">InjectRepository</span><span class="p">(</span><span class="nx">Customer</span><span class="p">)</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="nx">customersRepository</span><span class="p">:</span> <span class="nx">Repository</span><span class="o"><</span><span class="nx">Customer</span><span class="o">></span><span class="p">,</span>
<span class="p">)</span> <span class="p">{}</span>
<span class="k">async</span> <span class="nx">getByEmail</span><span class="p">(</span><span class="nx">email</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Customer</span><span class="o">></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">customer</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">customersRepository</span><span class="p">.</span><span class="nx">findOne</span><span class="p">({</span> <span class="nx">email</span> <span class="p">});</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">customer</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">customer</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">NotFoundException</span><span class="p">(</span><span class="dl">'</span><span class="s1">Customer with this email does not exist</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">async</span> <span class="nx">getById</span><span class="p">(</span><span class="nx">id</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Customer</span><span class="o">></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">customer</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">customersRepository</span><span class="p">.</span><span class="nx">findOne</span><span class="p">({</span> <span class="nx">id</span> <span class="p">});</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">customer</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">customer</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">NotFoundException</span><span class="p">(</span><span class="dl">'</span><span class="s1">Customer with this id does not exist</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">async</span> <span class="nx">create</span><span class="p">(</span><span class="nx">customerData</span><span class="p">:</span> <span class="nx">CreateCustomerDto</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Customer</span><span class="o">></span> <span class="p">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nx">Customer</span><span class="p">(</span><span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">customersRepository</span><span class="p">.</span><span class="nx">save</span><span class="p">(</span><span class="nx">customerData</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>In the above service, which holds our main application logic, we have three methods, namely: <em>getByEmail</em>, <em>getById</em> and <em>create</em>. As the names of the methods suggest, <em>getByEmail</em> retrieves the customer’s details using their email as a parameter, while the <em>getById</em> method fetches a customer’s details using their ID as a parameter. The <em>create</em> method creates a customer, and we shall see all the above methods in action in the next iteration of the tutorial.</p>
<h3 id="customer-entity">Customer Entity</h3>
<p>The customer entity file has remained unchanged from the one in previous tutorials:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/customers/entities/customer.entity.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/customers/entities/customer.entity.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/customers/entities/customer.entity.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/customers/entities/customer.entity.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Exclude</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">class-transformer</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Order</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../orders/entities/order.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span>
<span class="nx">Column</span><span class="p">,</span>
<span class="nx">CreateDateColumn</span><span class="p">,</span>
<span class="nx">Entity</span><span class="p">,</span>
<span class="nx">OneToMany</span><span class="p">,</span>
<span class="nx">PrimaryGeneratedColumn</span><span class="p">,</span>
<span class="nx">UpdateDateColumn</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Entity</span><span class="p">(</span><span class="dl">'</span><span class="s1">customers</span><span class="dl">'</span><span class="p">)</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">Customer</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span><span class="nx">intialData</span><span class="p">:</span> <span class="nb">Partial</span><span class="o"><</span><span class="nx">Customer</span><span class="o">></span> <span class="o">=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">intialData</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="nb">Object</span><span class="p">.</span><span class="nx">assign</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="nx">intialData</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">@</span><span class="nd">PrimaryGeneratedColumn</span><span class="p">(</span><span class="dl">'</span><span class="s1">uuid</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">id</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Column</span><span class="p">({</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">varchar</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Column</span><span class="p">({</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">varchar</span><span class="dl">'</span><span class="p">,</span> <span class="na">unique</span><span class="p">:</span> <span class="kc">true</span> <span class="p">})</span>
<span class="nx">email</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Column</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">phone_number</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">varchar</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">phoneNumber</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Exclude</span><span class="p">()</span>
<span class="p">@</span><span class="nd">Column</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">password_digest</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">varchar</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Exclude</span><span class="p">()</span>
<span class="nx">confirmPassword</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">CreateDateColumn</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">created_at</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">timestamptz</span><span class="dl">'</span><span class="p">,</span>
<span class="na">default</span><span class="p">:</span> <span class="dl">'</span><span class="s1">now()</span><span class="dl">'</span><span class="p">,</span>
<span class="na">readonly</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="p">})</span>
<span class="nx">createdAt</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">UpdateDateColumn</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">updated_at</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">timestamptz</span><span class="dl">'</span><span class="p">,</span>
<span class="na">default</span><span class="p">:</span> <span class="dl">'</span><span class="s1">now()</span><span class="dl">'</span><span class="p">,</span>
<span class="p">})</span>
<span class="nx">updatedAt</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">OneToMany</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">Order</span><span class="p">,</span> <span class="p">(</span><span class="nx">order</span><span class="p">)</span> <span class="o">=></span> <span class="nx">order</span><span class="p">.</span><span class="nx">customer</span><span class="p">)</span>
<span class="nx">orders</span><span class="p">:</span> <span class="nx">Order</span><span class="p">[];</span>
<span class="p">}</span></code></pre></figure>
<h3 id="customer-dtos">Customer DTOs</h3>
<p>Here are the customer DTO (Data Transfer Object) files.</p>
<p>Create Customer DTO:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/customers/dto/create-customer.dto.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/customers/dto/create-customer.dto.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/customers/dto/create-customer.dto.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/customers/dto/create-customer.dto.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">IsEmail</span><span class="p">,</span> <span class="nx">IsNotEmpty</span><span class="p">,</span> <span class="nx">IsString</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">class-validator</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">CreateCustomerDto</span> <span class="p">{</span>
<span class="p">@</span><span class="nd">IsEmail</span><span class="p">()</span>
<span class="p">@</span><span class="nd">IsString</span><span class="p">()</span>
<span class="p">@</span><span class="nd">IsNotEmpty</span><span class="p">()</span>
<span class="nx">email</span><span class="p">?:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">IsString</span><span class="p">()</span>
<span class="p">@</span><span class="nd">IsNotEmpty</span><span class="p">()</span>
<span class="nx">name</span><span class="p">?:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">IsString</span><span class="p">()</span>
<span class="p">@</span><span class="nd">IsNotEmpty</span><span class="p">()</span>
<span class="nx">password</span><span class="p">?:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">IsString</span><span class="p">()</span>
<span class="p">@</span><span class="nd">IsNotEmpty</span><span class="p">()</span>
<span class="nx">confirmPassword</span><span class="p">?:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">IsString</span><span class="p">()</span>
<span class="p">@</span><span class="nd">IsNotEmpty</span><span class="p">()</span>
<span class="nx">phoneNumber</span><span class="p">?:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>Update Customer DTO:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/customers/dto/update-customer.dto.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/customers/dto/update-customer.dto.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/customers/dto/update-customer.dto.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/customers/dto/update-customer.dto.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">PartialType</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/mapped-types</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CreateCustomerDto</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./create-customer.dto</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">UpdateCustomerDto</span> <span class="kd">extends</span> <span class="nx">PartialType</span><span class="p">(</span><span class="nx">CreateCustomerDto</span><span class="p">)</span> <span class="p">{}</span></code></pre></figure>
<p>Login Customer DTO:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/customers/dto/login-customer.dto.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/customers/dto/login-customer.dto.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/customers/dto/login-customer.dto.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/customers/dto/login-customer.dto.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">IsEmail</span><span class="p">,</span> <span class="nx">IsNotEmpty</span><span class="p">,</span> <span class="nx">IsString</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">class-validator</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">LoginCustomerDto</span> <span class="p">{</span>
<span class="p">@</span><span class="nd">IsString</span><span class="p">()</span>
<span class="p">@</span><span class="nd">IsNotEmpty</span><span class="p">()</span>
<span class="p">@</span><span class="nd">IsEmail</span><span class="p">()</span>
<span class="nx">email</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">IsString</span><span class="p">()</span>
<span class="p">@</span><span class="nd">IsNotEmpty</span><span class="p">()</span>
<span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>In Nest.js, DTOs are used to validate incoming data from a request before being proccessed by the Nest.js framework. They are usually generated by default when you create new resouces using the Nest.js CLI. The first two are for validating a customer details in the request body before creating and updating a customer respectively, while the last DTO is used when a customer logs in to our application, and will be used in a later tutorial. We’ll leave it in place for now.</p>
<h3 id="unit-tests">Unit Tests</h3>
<p>Since we have not created any controllers, we shall therefore have no need for an end to end test within this module. Instead, we shall create unit tests for our service logic, which look as follows:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/customers/spec/customers.service.spec.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/customers/spec/customers.service.spec.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/customers/spec/customers.service.spec.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/customers/spec/customers.service.spec.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">NotFoundException</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Test</span><span class="p">,</span> <span class="nx">TestingModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/testing</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">getRepositoryToken</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CustomersService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../customers.service</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">CreateCustomerDto</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../dto/create-customer.dto</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Customer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../entities/customer.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">CustomersService</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">service</span><span class="p">:</span> <span class="nx">CustomersService</span><span class="p">;</span>
<span class="nx">beforeEach</span><span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">createMock</span> <span class="o">=</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">((</span><span class="na">dto</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">dto</span><span class="p">;</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">saveMock</span> <span class="o">=</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">((</span><span class="na">dto</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">dto</span><span class="p">;</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">findOneMock</span> <span class="o">=</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">((</span><span class="na">dto</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">dto</span><span class="p">;</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">MockRepository</span> <span class="o">=</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">().</span><span class="nx">mockImplementation</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">create</span><span class="p">:</span> <span class="nx">createMock</span><span class="p">,</span>
<span class="na">save</span><span class="p">:</span> <span class="nx">saveMock</span><span class="p">,</span>
<span class="na">findOne</span><span class="p">:</span> <span class="nx">findOneMock</span><span class="p">,</span>
<span class="p">};</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="na">module</span><span class="p">:</span> <span class="nx">TestingModule</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">Test</span><span class="p">.</span><span class="nx">createTestingModule</span><span class="p">({</span>
<span class="na">providers</span><span class="p">:</span> <span class="p">[</span>
<span class="nx">CustomersService</span><span class="p">,</span>
<span class="p">{</span>
<span class="na">provide</span><span class="p">:</span> <span class="nx">getRepositoryToken</span><span class="p">(</span><span class="nx">Customer</span><span class="p">),</span>
<span class="na">useClass</span><span class="p">:</span> <span class="nx">MockRepository</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">],</span>
<span class="p">}).</span><span class="nx">compile</span><span class="p">();</span>
<span class="nx">service</span> <span class="o">=</span> <span class="kr">module</span><span class="p">.</span><span class="kd">get</span><span class="o"><</span><span class="nx">CustomersService</span><span class="o">></span><span class="p">(</span><span class="nx">CustomersService</span><span class="p">);</span>
<span class="p">});</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should be defined</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">expect</span><span class="p">(</span><span class="nx">service</span><span class="p">).</span><span class="nx">toBeDefined</span><span class="p">();</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">when getting a customer by email</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">customer</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Customer</span><span class="p">({});</span>
<span class="kd">const</span> <span class="nx">testEmail</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">test@test.com</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">and the customer is matched</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should return the customer</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">jest</span>
<span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">service</span><span class="p">,</span> <span class="dl">'</span><span class="s1">getByEmail</span><span class="dl">'</span><span class="p">)</span>
<span class="p">.</span><span class="nx">mockImplementation</span><span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="nx">customer</span><span class="p">);</span>
<span class="nx">expect</span><span class="p">(</span><span class="k">await</span> <span class="nx">service</span><span class="p">.</span><span class="nx">getByEmail</span><span class="p">(</span><span class="nx">testEmail</span><span class="p">)).</span><span class="nx">toBe</span><span class="p">(</span><span class="nx">customer</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">and the customer is not matched</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should throw an error</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">jest</span>
<span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">service</span><span class="p">,</span> <span class="dl">'</span><span class="s1">getByEmail</span><span class="dl">'</span><span class="p">)</span>
<span class="p">.</span><span class="nx">mockRejectedValue</span><span class="p">(</span>
<span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Customer with this email does not exist</span><span class="dl">'</span><span class="p">),</span>
<span class="p">);</span>
<span class="k">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">service</span><span class="p">.</span><span class="nx">getByEmail</span><span class="p">(</span><span class="nx">testEmail</span><span class="p">)).</span><span class="nx">rejects</span><span class="p">.</span><span class="nx">toThrow</span><span class="p">();</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">when getting a customer by id</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">customer</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Customer</span><span class="p">({});</span>
<span class="kd">const</span> <span class="nx">testId</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">59f78b6b-b1fb-4cf3-ade1-608f37a9d3fa</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">and the customer is matched</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should return the customer</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">jest</span><span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">service</span><span class="p">,</span> <span class="dl">'</span><span class="s1">getById</span><span class="dl">'</span><span class="p">).</span><span class="nx">mockImplementation</span><span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="nx">customer</span><span class="p">);</span>
<span class="nx">expect</span><span class="p">(</span><span class="k">await</span> <span class="nx">service</span><span class="p">.</span><span class="nx">getById</span><span class="p">(</span><span class="nx">testId</span><span class="p">)).</span><span class="nx">toBe</span><span class="p">(</span><span class="nx">customer</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">and the customer is not matched</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should throw an error</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">jest</span>
<span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">service</span><span class="p">,</span> <span class="dl">'</span><span class="s1">getById</span><span class="dl">'</span><span class="p">)</span>
<span class="p">.</span><span class="nx">mockRejectedValue</span><span class="p">(</span><span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Customer with this id does not exist</span><span class="dl">'</span><span class="p">));</span>
<span class="k">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">service</span><span class="p">.</span><span class="nx">getById</span><span class="p">(</span><span class="nx">testId</span><span class="p">)).</span><span class="nx">rejects</span><span class="p">.</span><span class="nx">toThrow</span><span class="p">();</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">create</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should return a new customer</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="na">customer</span><span class="p">:</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Customer</span><span class="o">></span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">testCreateCustomerDto</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">CreateCustomerDto</span><span class="p">();</span>
<span class="nx">jest</span><span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">service</span><span class="p">,</span> <span class="dl">'</span><span class="s1">create</span><span class="dl">'</span><span class="p">).</span><span class="nx">mockImplementation</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">customer</span><span class="p">);</span>
<span class="nx">expect</span><span class="p">(</span><span class="k">await</span> <span class="nx">service</span><span class="p">.</span><span class="nx">create</span><span class="p">(</span><span class="nx">testCreateCustomerDto</span><span class="p">)).</span><span class="nx">toBe</span><span class="p">(</span><span class="nx">customer</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p">});</span></code></pre></figure>
<p>We are using the <a href="https://jestjs.io/docs/getting-started">Jest testing framework</a>, which comes bundled with Nest.js. The <em>describe</em> blocks explain what the specific unit test is required to perform.</p>
<p>When we run our tests, we should get an all green:</p>
<p><img src="/assets/storefront/26-customer-spec.png" alt="Customer Spec" /></p>Isaiah MucheneIn this iteration of the tutorial, we shall be creating the customer module for our backend application. In case of any issues, you can refer to my Github repositoryCreate Nest.js Seeders2022-06-14T16:22:00+03:002022-06-14T16:22:00+03:00/2022/06/create-nestjs-seeders<div class="card seriesNote">
<p class="text-center">
This article is <strong>Part 4</strong> in a <strong>10 Part</strong> Series.
</p>
<ul class="list-group list-group-flush">
<li class="list-group-item"> Part 1 -
<a href="/2022/04/bootstrapping-nestjs-ubuntu">Bootstrapping a Nest.js Project on Ubuntu</a>
</li>
<li class="list-group-item"> Part 2 -
<a href="/2022/04/create-nestjs-migrations">Create Nest.js Migrations</a>
</li>
<li class="list-group-item"> Part 3 -
<a href="/2022/04/create-nestjs-crud-resources">Create Nest.js CRUD Resources</a>
</li>
<li class="list-group-item"> Part 4 -
This Article
</li>
<li class="list-group-item"> Part 5 -
<a href="/2022/07/customer-module">Create a Customer Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 6 -
<a href="/2022/07/auth-module">Nest.js Authentication</a>
</li>
<li class="list-group-item"> Part 7 -
<a href="/2022/07/products-module">Create a Products Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 8 -
<a href="/2022/07/stripe-module">Create a Stripe Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 9 -
<a href="/2022/08/orders-module">Create an Orders Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 10 -
<a href="/2022/08/end-to-end-tests">End to End Tests for our Storefront Backend</a>
</li>
</ul>
</div>
<p><br /></p>
<p>In the <a href="/2022/04/create-nestjs-crud-resources">previous tutorial</a>, we created the CRUD resources as well as the entities that our storefront backend would require. Today, we shall be creating a special class called a <a href="https://en.wikipedia.org/wiki/Database_seeding">database seeder</a>, which is used for populating a database with some initial data, usually for testing or development purposes, without having to manually key in data (which is quite tedious).</p>
<p>For our project, we shall be using the <a href="https://www.npmjs.com/package/typeorm-seeding">typeorm-seeding</a> library, which I have found out to be the most straightforward.</p>
<p>In case of any issues, you can refer to my Github repository <a href="https://github.com/imuchene/storefront-backend">here</a>.</p>
<h3 id="add-the-typeorm-seeder-package-but-let-it-only-work-in-the-development-environment">Add the typeorm seeder package (but let it only work in the development environment)</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i typeorm-seeding <span class="nt">--save-dev</span>
</code></pre></div></div>
<h3 id="create-a-packagejson-entry-for-the-seeder-tasks">Create a package.json entry for the seeder tasks</h3>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/package.json">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
package.json <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/package.json">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/package.json">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="nl">"seed:config"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ts-node ./node_modules/typeorm-seeding/dist/cli.js config"</span><span class="err">,</span><span class="w">
</span><span class="nl">"seed:run"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ts-node ./node_modules/typeorm-seeding/dist/cli.js seed"</span></code></pre></figure>
<h3 id="check-the-seeder-configuration">Check the seeder configuration</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm run seed:config
</code></pre></div></div>
<p>Output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>🌱 TypeORM Seeding v1.6.1
<span class="o">{</span>
<span class="nb">type</span>: <span class="s1">'postgres'</span>,
logging: <span class="s1">'true'</span>,
host: <span class="s1">'localhost'</span>,
port: 5432,
username: <span class="s1">'storefront_username'</span>,
password: <span class="s1">'storefront_password'</span>,
database: <span class="s1">'storefront_database'</span>,
autoLoadEntities: <span class="nb">true</span>,
ssl: <span class="o">{</span> rejectUnauthorized: <span class="nb">false</span> <span class="o">}</span>,
entities: <span class="o">[</span>
<span class="s1">'/home/my_profile/Projects/nest_projects/storefront-backend/src/modules/**/*.entity{.ts,.js}'</span>,
<span class="s1">'/home/my_profile/Projects/nest_projects/storefront-backend/src/modules/**/*.view{.ts,.js}'</span>
<span class="o">]</span>,
migrations: <span class="o">[</span>
<span class="s1">'/home/my_profile/Projects/nest_projects/storefront-backend/src/migrations/**/*{.ts,.js}'</span>
<span class="o">]</span>,
cli: <span class="o">{</span>
migrationsDir: <span class="s1">'/home/my_profile/Projects/nest_projects/storefront-backend/src//migrations'</span>
<span class="o">}</span>,
baseDirectory: <span class="s1">'/home/my_profile/Projects/nest_projects/storefront-backend'</span>,
factories: <span class="o">[</span> <span class="s1">'src/database/factories/**/*{.ts,.js}'</span> <span class="o">]</span>,
seeds: <span class="o">[</span> <span class="s1">'src/database/seeds/**/*{.ts,.js}'</span> <span class="o">]</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="create-the-seeder-ive-called-mine-create-productsseed">Create the seeder (I’ve called mine create-products.seed)</h3>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/database/seeds/create-products.seed.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/database/seeds/create-products.seed.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/database/seeds/create-products.seed.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/database/seeds/create-products.seed.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Product</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../modules/products/entities/product.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Connection</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Factory</span><span class="p">,</span> <span class="nx">Seeder</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">typeorm-seeding</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">class</span> <span class="nx">CreateProducts</span> <span class="k">implements</span> <span class="nx">Seeder</span> <span class="p">{</span>
<span class="k">public</span> <span class="k">async</span> <span class="nx">run</span><span class="p">(</span><span class="nx">factory</span><span class="p">:</span> <span class="nx">Factory</span><span class="p">,</span> <span class="nx">connection</span><span class="p">:</span> <span class="nx">Connection</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="kr">any</span><span class="o">></span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">connection</span>
<span class="p">.</span><span class="nx">createQueryBuilder</span><span class="p">()</span>
<span class="p">.</span><span class="nx">insert</span><span class="p">()</span>
<span class="p">.</span><span class="nx">into</span><span class="p">(</span><span class="nx">Product</span><span class="p">)</span>
<span class="p">.</span><span class="nx">values</span><span class="p">([</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Berries</span><span class="dl">'</span><span class="p">,</span>
<span class="na">unitPrice</span><span class="p">:</span> <span class="mf">23.54</span><span class="p">,</span>
<span class="na">description</span><span class="p">:</span>
<span class="dl">'</span><span class="s1">The bestest fruit known to man. Sweet yet sour but beautiful</span><span class="dl">'</span><span class="p">,</span>
<span class="na">image</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/assets/images/berries.jpeg</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Orange</span><span class="dl">'</span><span class="p">,</span>
<span class="na">unitPrice</span><span class="p">:</span> <span class="mf">10.33</span><span class="p">,</span>
<span class="na">description</span><span class="p">:</span> <span class="s2">`Succulent and watery, you'll never run out of water`</span><span class="p">,</span>
<span class="na">image</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/assets/images/oranges.jpeg</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Lemons</span><span class="dl">'</span><span class="p">,</span>
<span class="na">unitPrice</span><span class="p">:</span> <span class="mf">12.13</span><span class="p">,</span>
<span class="na">description</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Sour but important for revitalization</span><span class="dl">'</span><span class="p">,</span>
<span class="na">image</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/assets/images/lemons.jpeg</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Bananas</span><span class="dl">'</span><span class="p">,</span>
<span class="na">unitPrice</span><span class="p">:</span> <span class="mf">10.33</span><span class="p">,</span>
<span class="na">description</span><span class="p">:</span> <span class="dl">'</span><span class="s1">An every day fruit, can be served with every dish</span><span class="dl">'</span><span class="p">,</span>
<span class="na">image</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/assets/images/banana.jpeg</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Apples</span><span class="dl">'</span><span class="p">,</span>
<span class="na">unitPrice</span><span class="p">:</span> <span class="mf">10.33</span><span class="p">,</span>
<span class="na">description</span><span class="p">:</span>
<span class="dl">'</span><span class="s1">Sliced and served with your salad. Served as snacks midway through the day</span><span class="dl">'</span><span class="p">,</span>
<span class="na">image</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/assets/images/apple-item.png</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Sharifa</span><span class="dl">'</span><span class="p">,</span>
<span class="na">unitPrice</span><span class="p">:</span> <span class="mf">10.33</span><span class="p">,</span>
<span class="na">description</span><span class="p">:</span> <span class="dl">'</span><span class="s1">A great fruit, also known as custard apple</span><span class="dl">'</span><span class="p">,</span>
<span class="na">image</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/assets/images/unknown.jpeg</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">])</span>
<span class="p">.</span><span class="nx">execute</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<h3 id="run-the-seeder">Run the seeder</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm run seed:run
</code></pre></div></div>
<p>Output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>🌱 TypeORM Seeding v1.6.1
✔ ORM Config loaded
✔ Factories are imported
✔ Seeders are imported
✔ Database connected
✔ Seeder CreateProducts executed
👍 Finished Seeding
</code></pre></div></div>
<p>Now, when you run a SELECT query on the products table, you’ll notice that the table now has some initial data:</p>
<p><img src="/assets/storefront/08-seeded-products.png" alt="Products Table" /></p>Isaiah MucheneIn the previous tutorial, we created the CRUD resources as well as the entities that our storefront backend would require. Today, we shall be creating a special class called a database seederCreate Nest.js CRUD Resources2022-04-13T16:41:00+03:002022-04-13T16:41:00+03:00/2022/04/create-nestjs-crud-resources<div class="card seriesNote">
<p class="text-center">
This article is <strong>Part 3</strong> in a <strong>10 Part</strong> Series.
</p>
<ul class="list-group list-group-flush">
<li class="list-group-item"> Part 1 -
<a href="/2022/04/bootstrapping-nestjs-ubuntu">Bootstrapping a Nest.js Project on Ubuntu</a>
</li>
<li class="list-group-item"> Part 2 -
<a href="/2022/04/create-nestjs-migrations">Create Nest.js Migrations</a>
</li>
<li class="list-group-item"> Part 3 -
This Article
</li>
<li class="list-group-item"> Part 4 -
<a href="/2022/06/create-nestjs-seeders">Create Nest.js Seeders</a>
</li>
<li class="list-group-item"> Part 5 -
<a href="/2022/07/customer-module">Create a Customer Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 6 -
<a href="/2022/07/auth-module">Nest.js Authentication</a>
</li>
<li class="list-group-item"> Part 7 -
<a href="/2022/07/products-module">Create a Products Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 8 -
<a href="/2022/07/stripe-module">Create a Stripe Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 9 -
<a href="/2022/08/orders-module">Create an Orders Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 10 -
<a href="/2022/08/end-to-end-tests">End to End Tests for our Storefront Backend</a>
</li>
</ul>
</div>
<p><br /></p>
<p>Continuing from where we left off in the <a href="/2022/04/create-nestjs-migrations">previous tutorial</a>, we shall be creating CRUD resources for our store in Nest.js.js. <a href="https://www.javatpoint.com/crud-operations-in-sql">CRUD</a> resources help us accelerate our application development time by autogenerating some much needed classes, such as entities, controllers and services that we will be using later on in our application development. It also bundles all the generated code into a single directory which allows it to be tested independently of other resources.</p>
<p>Entities are code representations of database objects, usually tables and views. They allow us to perform database operations, such as CRUD operations, but without having to write SQL code. Controllers handle requests and responses from calling applications such as web browsers, and map an incoming request, such as GET or POST request to our backend logic. A service in Nest.js does the “heavy lifting” by doing all the required processing brought in by the request and outputting a response back to the controller. All our resources will be contained within a modules directory.</p>
<p>In case of any issues, you can refer to my Github repository <a href="https://github.com/imuchene/storefront-backend">here</a>.</p>
<h3 id="generate-the-crud-resources">Generate the CRUD resources</h3>
<p>Let’s start by creating CRUD resources for each part of our application (authentication, orders, products and customers). In addition to these, for security reasons, we will create an additional authentication module to assist in handling customer authentication (registration, logins and logouts).</p>
<p>Within the project root run:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nest g resource modules/orders
</code></pre></div></div>
<p>Output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>? What transport layer <span class="k">do </span>you use? REST API
? Would you like to generate CRUD entry points? Yes
CREATE src/modules/orders/orders.controller.spec.ts <span class="o">(</span>576 bytes<span class="o">)</span>
CREATE src/modules/orders/orders.controller.ts <span class="o">(</span>915 bytes<span class="o">)</span>
CREATE src/modules/orders/orders.module.ts <span class="o">(</span>254 bytes<span class="o">)</span>
CREATE src/modules/orders/orders.service.spec.ts <span class="o">(</span>460 bytes<span class="o">)</span>
CREATE src/modules/orders/orders.service.ts <span class="o">(</span>623 bytes<span class="o">)</span>
CREATE src/modules/orders/dto/create-order.dto.ts <span class="o">(</span>31 bytes<span class="o">)</span>
CREATE src/modules/orders/dto/update-order.dto.ts <span class="o">(</span>173 bytes<span class="o">)</span>
CREATE src/modules/orders/entities/order.entity.ts <span class="o">(</span>22 bytes<span class="o">)</span>
UPDATE src/app.module.ts <span class="o">(</span>1180 bytes<span class="o">)</span>
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nest g resource modules/products
</code></pre></div></div>
<p>Output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>? What transport layer <span class="k">do </span>you use? REST API
? Would you like to generate CRUD entry points? Yes
CREATE src/modules/products/products.controller.spec.ts <span class="o">(</span>596 bytes<span class="o">)</span>
CREATE src/modules/products/products.controller.ts <span class="o">(</span>957 bytes<span class="o">)</span>
CREATE src/modules/products/products.module.ts <span class="o">(</span>268 bytes<span class="o">)</span>
CREATE src/modules/products/products.service.spec.ts <span class="o">(</span>474 bytes<span class="o">)</span>
CREATE src/modules/products/products.service.ts <span class="o">(</span>651 bytes<span class="o">)</span>
CREATE src/modules/products/dto/create-product.dto.ts <span class="o">(</span>33 bytes<span class="o">)</span>
CREATE src/modules/products/dto/update-product.dto.ts <span class="o">(</span>181 bytes<span class="o">)</span>
CREATE src/modules/products/entities/product.entity.ts <span class="o">(</span>24 bytes<span class="o">)</span>
UPDATE package.json <span class="o">(</span>2618 bytes<span class="o">)</span>
UPDATE src/app.module.ts <span class="o">(</span>1117 bytes<span class="o">)</span>
✔ Packages installed successfully.
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nest g resource modules/customers
</code></pre></div></div>
<p>Output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>? What transport layer <span class="k">do </span>you use? REST API
? Would you like to generate CRUD entry points? Yes
CREATE src/modules/customers/customers.controller.spec.ts <span class="o">(</span>606 bytes<span class="o">)</span>
CREATE src/modules/customers/customers.controller.ts <span class="o">(</span>978 bytes<span class="o">)</span>
CREATE src/modules/customers/customers.module.ts <span class="o">(</span>275 bytes<span class="o">)</span>
CREATE src/modules/customers/customers.service.spec.ts <span class="o">(</span>481 bytes<span class="o">)</span>
CREATE src/modules/customers/customers.service.ts <span class="o">(</span>665 bytes<span class="o">)</span>
CREATE src/modules/customers/dto/create-customer.dto.ts <span class="o">(</span>34 bytes<span class="o">)</span>
CREATE src/modules/customers/dto/update-customer.dto.ts <span class="o">(</span>185 bytes<span class="o">)</span>
CREATE src/modules/customers/entities/customer.entity.ts <span class="o">(</span>25 bytes<span class="o">)</span>
UPDATE src/app.module.ts <span class="o">(</span>1120 bytes<span class="o">)</span>
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nest g resource modules/auth
</code></pre></div></div>
<p>Output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CREATE src/modules/auth/auth.module.ts <span class="o">(</span>81 bytes<span class="o">)</span>
UPDATE src/app.module.ts <span class="o">(</span>1121 bytes<span class="o">)</span>
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nest g controller modules/auth
</code></pre></div></div>
<p>Output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CREATE src/modules/auth/auth.controller.spec.ts <span class="o">(</span>478 bytes<span class="o">)</span>
CREATE src/modules/auth/auth.controller.ts <span class="o">(</span>97 bytes<span class="o">)</span>
UPDATE src/modules/auth/auth.module.ts <span class="o">(</span>240 bytes<span class="o">)</span>
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nest g service modules/auth
</code></pre></div></div>
<p>Output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CREATE src/modules/auth/auth.service.spec.ts <span class="o">(</span>446 bytes<span class="o">)</span>
CREATE src/modules/auth/auth.service.ts <span class="o">(</span>88 bytes<span class="o">)</span>
UPDATE src/modules/auth/auth.module.ts <span class="o">(</span>155 bytes<span class="o">)</span>
</code></pre></div></div>
<p>For all the above resources and modules, I have created a spec directory in each where I have moved all .spec.ts files (spec files) for easier management. For reference, kindly check on my Github repository.</p>
<h3 id="create-the-entities">Create the entities</h3>
<p>Under the respective CRUD resources, we shall start by creating all our entities, and entity relationships. The entities will be contained within an entities directory:</p>
<p>Customer entity:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/customers/entities/customer.entity.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/customers/entities/customer.entity.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/customers/entities/customer.entity.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/customers/entities/customer.entity.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Exclude</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">class-transformer</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Order</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../orders/entities/order.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span>
<span class="nx">Column</span><span class="p">,</span>
<span class="nx">CreateDateColumn</span><span class="p">,</span>
<span class="nx">Entity</span><span class="p">,</span>
<span class="nx">OneToMany</span><span class="p">,</span>
<span class="nx">PrimaryGeneratedColumn</span><span class="p">,</span>
<span class="nx">UpdateDateColumn</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Entity</span><span class="p">(</span><span class="dl">'</span><span class="s1">customers</span><span class="dl">'</span><span class="p">)</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">Customer</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span><span class="nx">intialData</span><span class="p">:</span> <span class="nb">Partial</span><span class="o"><</span><span class="nx">Customer</span><span class="o">></span> <span class="o">=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">intialData</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="nb">Object</span><span class="p">.</span><span class="nx">assign</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="nx">intialData</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">@</span><span class="nd">PrimaryGeneratedColumn</span><span class="p">(</span><span class="dl">'</span><span class="s1">uuid</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">id</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Column</span><span class="p">({</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">varchar</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Column</span><span class="p">({</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">varchar</span><span class="dl">'</span><span class="p">,</span> <span class="na">unique</span><span class="p">:</span> <span class="kc">true</span> <span class="p">})</span>
<span class="nx">email</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Column</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">phone_number</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">varchar</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">phoneNumber</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Exclude</span><span class="p">()</span>
<span class="p">@</span><span class="nd">Column</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">password_digest</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">varchar</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Exclude</span><span class="p">()</span>
<span class="nx">confirmPassword</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">CreateDateColumn</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">created_at</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">timestamptz</span><span class="dl">'</span><span class="p">,</span>
<span class="na">default</span><span class="p">:</span> <span class="dl">'</span><span class="s1">now()</span><span class="dl">'</span><span class="p">,</span>
<span class="na">readonly</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="p">})</span>
<span class="nx">createdAt</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">UpdateDateColumn</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">updated_at</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">timestamptz</span><span class="dl">'</span><span class="p">,</span>
<span class="na">default</span><span class="p">:</span> <span class="dl">'</span><span class="s1">now()</span><span class="dl">'</span><span class="p">,</span>
<span class="p">})</span>
<span class="nx">updatedAt</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">OneToMany</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">Order</span><span class="p">,</span> <span class="p">(</span><span class="nx">order</span><span class="p">)</span> <span class="o">=></span> <span class="nx">order</span><span class="p">.</span><span class="nx">customer</span><span class="p">)</span>
<span class="nx">orders</span><span class="p">:</span> <span class="nx">Order</span><span class="p">[];</span>
<span class="p">}</span></code></pre></figure>
<p>Product entity:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/products/entities/product.entity.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/products/entities/product.entity.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/products/entities/product.entity.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/products/entities/product.entity.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">OrderItem</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../orders/entities/order-item.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span>
<span class="nx">Column</span><span class="p">,</span>
<span class="nx">CreateDateColumn</span><span class="p">,</span>
<span class="nx">DeleteDateColumn</span><span class="p">,</span>
<span class="nx">Entity</span><span class="p">,</span>
<span class="nx">OneToMany</span><span class="p">,</span>
<span class="nx">PrimaryGeneratedColumn</span><span class="p">,</span>
<span class="nx">UpdateDateColumn</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Entity</span><span class="p">(</span><span class="dl">'</span><span class="s1">products</span><span class="dl">'</span><span class="p">)</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">Product</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span><span class="nx">intialData</span><span class="p">:</span> <span class="nb">Partial</span><span class="o"><</span><span class="nx">Product</span><span class="o">></span> <span class="o">=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">intialData</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="nb">Object</span><span class="p">.</span><span class="nx">assign</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="nx">intialData</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">@</span><span class="nd">PrimaryGeneratedColumn</span><span class="p">(</span><span class="dl">'</span><span class="s1">uuid</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">id</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Column</span><span class="p">({</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">varchar</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Column</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">unit_price</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">numeric</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">unitPrice</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Column</span><span class="p">({</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">text</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">description</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Column</span><span class="p">({</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">varchar</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">image</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">CreateDateColumn</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">created_at</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">timestamptz</span><span class="dl">'</span><span class="p">,</span>
<span class="na">default</span><span class="p">:</span> <span class="dl">'</span><span class="s1">now()</span><span class="dl">'</span><span class="p">,</span>
<span class="na">readonly</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="p">})</span>
<span class="nx">createdAt</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">UpdateDateColumn</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">updated_at</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">timestamptz</span><span class="dl">'</span><span class="p">,</span>
<span class="na">default</span><span class="p">:</span> <span class="dl">'</span><span class="s1">now()</span><span class="dl">'</span><span class="p">,</span>
<span class="p">})</span>
<span class="nx">updatedAt</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">DeleteDateColumn</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">deleted_at</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">timestamptz</span><span class="dl">'</span><span class="p">,</span>
<span class="p">})</span>
<span class="nx">deletedAt</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">OneToMany</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">OrderItem</span><span class="p">,</span> <span class="p">(</span><span class="nx">orderItem</span><span class="p">)</span> <span class="o">=></span> <span class="nx">orderItem</span><span class="p">.</span><span class="nx">product</span><span class="p">)</span>
<span class="nx">orderItems</span><span class="p">:</span> <span class="nx">OrderItem</span><span class="p">[];</span>
<span class="p">}</span></code></pre></figure>
<p>Order entity:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/entities/order.entity.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/orders/entities/order.entity.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/entities/order.entity.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/entities/order.entity.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Customer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../customers/entities/customer.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span>
<span class="nx">Column</span><span class="p">,</span>
<span class="nx">CreateDateColumn</span><span class="p">,</span>
<span class="nx">Entity</span><span class="p">,</span>
<span class="nx">JoinColumn</span><span class="p">,</span>
<span class="nx">ManyToOne</span><span class="p">,</span>
<span class="nx">OneToMany</span><span class="p">,</span>
<span class="nx">PrimaryGeneratedColumn</span><span class="p">,</span>
<span class="nx">UpdateDateColumn</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">OrderItem</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./order-item.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">PaymentStatus</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../../common/enums/payment-status.enum</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Entity</span><span class="p">(</span><span class="dl">'</span><span class="s1">orders</span><span class="dl">'</span><span class="p">)</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">Order</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span><span class="nx">intialData</span><span class="p">:</span> <span class="nb">Partial</span><span class="o"><</span><span class="nx">Order</span><span class="o">></span> <span class="o">=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">intialData</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="nb">Object</span><span class="p">.</span><span class="nx">assign</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="nx">intialData</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">@</span><span class="nd">PrimaryGeneratedColumn</span><span class="p">(</span><span class="dl">'</span><span class="s1">uuid</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">id</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Column</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">customer_id</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">uuid</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">customerId</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Column</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">total_amount</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">numeric</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">totalAmount</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Column</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">payment_status</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">varchar</span><span class="dl">'</span><span class="p">,</span>
<span class="na">default</span><span class="p">:</span> <span class="nx">PaymentStatus</span><span class="p">.</span><span class="nx">Created</span><span class="p">,</span>
<span class="p">})</span>
<span class="nx">paymentStatus</span><span class="p">:</span> <span class="nx">PaymentStatus</span><span class="p">;</span>
<span class="p">@</span><span class="nd">CreateDateColumn</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">created_at</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">timestamptz</span><span class="dl">'</span><span class="p">,</span>
<span class="na">default</span><span class="p">:</span> <span class="dl">'</span><span class="s1">now()</span><span class="dl">'</span><span class="p">,</span>
<span class="na">readonly</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="p">})</span>
<span class="nx">createdAt</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">UpdateDateColumn</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">updated_at</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">timestamptz</span><span class="dl">'</span><span class="p">,</span>
<span class="na">default</span><span class="p">:</span> <span class="dl">'</span><span class="s1">now()</span><span class="dl">'</span><span class="p">,</span>
<span class="p">})</span>
<span class="nx">updatedAt</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">ManyToOne</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">Customer</span><span class="p">,</span> <span class="p">(</span><span class="nx">customer</span><span class="p">)</span> <span class="o">=></span> <span class="nx">customer</span><span class="p">.</span><span class="nx">orders</span><span class="p">)</span>
<span class="p">@</span><span class="nd">JoinColumn</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">customer_id</span><span class="dl">'</span><span class="p">,</span> <span class="na">referencedColumnName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">id</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">customer</span><span class="p">:</span> <span class="nx">Customer</span><span class="p">;</span>
<span class="p">@</span><span class="nd">OneToMany</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">OrderItem</span><span class="p">,</span> <span class="p">(</span><span class="nx">orderItem</span><span class="p">)</span> <span class="o">=></span> <span class="nx">orderItem</span><span class="p">.</span><span class="nx">order</span><span class="p">,</span> <span class="p">{</span> <span class="na">cascade</span><span class="p">:</span> <span class="kc">true</span> <span class="p">})</span>
<span class="nx">orderItems</span><span class="p">:</span> <span class="nx">OrderItem</span><span class="p">[];</span>
<span class="p">}</span></code></pre></figure>
<p>Order item entity:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/entities/order-item.entity.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/modules/orders/entities/order-item.entity.ts <a href="https://github.com/imuchene/storefront-backend/blob/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/entities/order-item.entity.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/59dfd9079ec92f67e23a146bca360a0e5b473c77/src/modules/orders/entities/order-item.entity.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Product</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../products/entities/product.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Column</span><span class="p">,</span> <span class="nx">Entity</span><span class="p">,</span> <span class="nx">JoinColumn</span><span class="p">,</span> <span class="nx">ManyToOne</span><span class="p">,</span> <span class="nx">PrimaryColumn</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Order</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./order.entity</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Entity</span><span class="p">(</span><span class="dl">'</span><span class="s1">order_items</span><span class="dl">'</span><span class="p">)</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">OrderItem</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span><span class="nx">intialData</span><span class="p">:</span> <span class="nb">Partial</span><span class="o"><</span><span class="nx">OrderItem</span><span class="o">></span> <span class="o">=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">intialData</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="nb">Object</span><span class="p">.</span><span class="nx">assign</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="nx">intialData</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">@</span><span class="nd">PrimaryColumn</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">order_id</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">uuid</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">orderId</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">PrimaryColumn</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">product_id</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">uuid</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">productId</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Column</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">unit_price</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">numeric</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">unitPrice</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Column</span><span class="p">({</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">numeric</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">quantity</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">@</span><span class="nd">ManyToOne</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">Product</span><span class="p">,</span> <span class="p">(</span><span class="nx">product</span><span class="p">)</span> <span class="o">=></span> <span class="nx">product</span><span class="p">.</span><span class="nx">orderItems</span><span class="p">)</span>
<span class="p">@</span><span class="nd">JoinColumn</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">product_id</span><span class="dl">'</span><span class="p">,</span> <span class="na">referencedColumnName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">id</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">product</span><span class="p">:</span> <span class="nx">Product</span><span class="p">;</span>
<span class="p">@</span><span class="nd">ManyToOne</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">Order</span><span class="p">,</span> <span class="p">(</span><span class="nx">order</span><span class="p">)</span> <span class="o">=></span> <span class="nx">order</span><span class="p">.</span><span class="nx">orderItems</span><span class="p">)</span>
<span class="p">@</span><span class="nd">JoinColumn</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">order_id</span><span class="dl">'</span><span class="p">,</span> <span class="na">referencedColumnName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">id</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">order</span><span class="p">:</span> <span class="nx">Order</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>In all the above entities, I have also modelled the 1 to many, as well as many to 1 relationships that we had in our entity relationship diagram.</p>
<h3 id="sources">SOURCES</h3>
<ul>
<li>TypeORM Entities Documentation: <a href="https://typeorm.io/entities">https://typeorm.io/entities</a></li>
<li>TypeORM Repository Documentation: <a href="https://typeorm.io/working-with-repository">https://typeorm.io/working-with-repository</a></li>
<li>Nest.js Documentation: <a href="https://docs.nestjs.com/techniques/database">https://docs.nestjs.com/techniques/database</a></li>
<li>Trilon blog on Resource Generators: <a href="https://trilon.io/blog/introducing-cli-generators-crud-api-in-1-minute">https://trilon.io/blog/introducing-cli-generators-crud-api-in-1-minute</a></li>
</ul>Isaiah MucheneContinuing from where we left off in the previous tutorial, we shall be creating CRUD resources for our store in Nest.js.Create Nest.js Migrations2022-04-08T17:06:00+03:002022-04-08T17:06:00+03:00/2022/04/create-nestjs-migrations<div class="card seriesNote">
<p class="text-center">
This article is <strong>Part 2</strong> in a <strong>10 Part</strong> Series.
</p>
<ul class="list-group list-group-flush">
<li class="list-group-item"> Part 1 -
<a href="/2022/04/bootstrapping-nestjs-ubuntu">Bootstrapping a Nest.js Project on Ubuntu</a>
</li>
<li class="list-group-item"> Part 2 -
This Article
</li>
<li class="list-group-item"> Part 3 -
<a href="/2022/04/create-nestjs-crud-resources">Create Nest.js CRUD Resources</a>
</li>
<li class="list-group-item"> Part 4 -
<a href="/2022/06/create-nestjs-seeders">Create Nest.js Seeders</a>
</li>
<li class="list-group-item"> Part 5 -
<a href="/2022/07/customer-module">Create a Customer Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 6 -
<a href="/2022/07/auth-module">Nest.js Authentication</a>
</li>
<li class="list-group-item"> Part 7 -
<a href="/2022/07/products-module">Create a Products Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 8 -
<a href="/2022/07/stripe-module">Create a Stripe Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 9 -
<a href="/2022/08/orders-module">Create an Orders Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 10 -
<a href="/2022/08/end-to-end-tests">End to End Tests for our Storefront Backend</a>
</li>
</ul>
</div>
<p><br /></p>
<p>We shall be continuing from where we left off in the previous tutorial by building the database backend (using Postgresql) for the storefront backend. We shall start off by building migrations for the application. Migrations in Nest.js are code representations of changes that we wish to make to our database, which we can track via version control. But before we build the migraions, we shall start by doing a basic design of the database using <a href="https://sqldbm.com/Home/">SQLDBM</a>. We will be using <a href="http://typeorm.io/">TypeORM</a>, which is a well known Typescript ORM (Object Relational Mapper) that we will have to add to our project to assist in the creation of the migrations. We will also be adding the <a href="https://docs.nestjs.com/techniques/configuration">Nest.js configuration module</a> to help us securely store our database credentials, as well as validate our environment variables.</p>
<p>In case of any issues, you can refer to my Github repository <a href="https://github.com/imuchene/storefront-backend">here</a>.</p>
<h3 id="entity-relationship-diagram">Entity Relationship Diagram</h3>
<p>SQLDBM is a cloud based database modelling tool that’s easy to use and free to use. Using some of the examples provided, I was able to design the Entity Relationship Diagram below:</p>
<p><img src="/assets/storefront/05-SQLDBM-Database-Modelling-v4.png" alt="SQLDBM ERD" /></p>
<p>We can now proceed to implement the above in our codebase.</p>
<h3 id="install-typeorm-plus-additional-dependencies">Install Typeorm, plus additional dependencies</h3>
<p>Navigate to the project directory.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> ~/Projects/nest_projects/storefront-backend
</code></pre></div></div>
<p>Install typeorm, the nest.js typeorm integration (that allows you to use typeorm entities/repositories within nest.js modules), the node postgres library, reflect-metadata (which is a typeorm dependency), as well as the typescript definitions for node.js.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i @nestjs/typeorm typeorm@0.2 pg reflect-metadata @types/node <span class="nt">--save</span>
</code></pre></div></div>
<h3 id="install-the-nestjs-config-module">Install the nest.js config module</h3>
<p>We will need to install the configuration package module for Nest.js, which will allow us to inject ConfigModule and ConfigService into our modules and services.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i @nestjs/config <span class="nt">--save</span>
</code></pre></div></div>
<h3 id="install-the-class-validator-and-class-transformer-packages">Install the class-validator and class-transformer packages</h3>
<p>These packages will be used to validate environment variables, as we shall see in a minute.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i class-transformer class-validator <span class="nt">--save</span>
</code></pre></div></div>
<h3 id="run-the-following-commands-to-create-the-postgres-user-and-database-as-the-postgres-admin-user">Run the following commands to create the postgres user and database (as the postgres admin user)</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo</span> <span class="nt">-u</span> postgres psql
</code></pre></div></div>
<p>Inside the postgres console, run:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>create user storefront with encrypted password <span class="s1">'your_database_password'</span><span class="p">;</span>
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>create database storefront with owner <span class="s1">'storefront'</span><span class="p">;</span>
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>grant all on database storefront to storefront<span class="p">;</span>
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>alter user storefront with createdb<span class="p">;</span>
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="se">\c</span> storefront
</code></pre></div></div>
<p>Since we’ll be using UUIDs as our primary keys, we’ll need to load additional postgres extensions:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CREATE EXTENSION IF NOT EXISTS <span class="s2">"uuid-ossp"</span> WITH SCHEMA public<span class="p">;</span>
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CREATE EXTENSION IF NOT EXISTS <span class="s2">"pgcrypto"</span> WITH SCHEMA public<span class="p">;</span>
</code></pre></div></div>
<p>Exit the postgres console.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="se">\q</span>
</code></pre></div></div>
<h3 id="test-that-the-database-credentials-work-via-the-command-line">Test that the database credentials work via the command line</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>psql <span class="nt">-h</span> localhost <span class="nt">-U</span> storefront
</code></pre></div></div>
<p>Enter your database password:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Password <span class="k">for </span>user storefront:
</code></pre></div></div>
<p>Once you’ve successfully entered your password, you should see the following printed on your console:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>psql <span class="o">(</span>14.2 <span class="o">(</span>Ubuntu 14.2-1.pgdg21.04+1<span class="o">)</span>, server 13.6 <span class="o">(</span>Ubuntu 13.6-1.pgdg21.04+1<span class="o">))</span>
SSL connection <span class="o">(</span>protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off<span class="o">)</span>
Type <span class="s2">"help"</span> <span class="k">for </span>help.
<span class="nv">storefront</span><span class="o">=></span>
</code></pre></div></div>
<h3 id="add-typeorm-to-the-main-application-module-and-configure-environment-variables">Add TypeORM to the main application Module and configure environment variables</h3>
<p>Modify your app module like so:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/12c7a3e750027c39424d95c115b738a8bc6f1a83/src/app.module.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/app.module.ts <a href="https://github.com/imuchene/storefront-backend/blob/12c7a3e750027c39424d95c115b738a8bc6f1a83/src/app.module.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/12c7a3e750027c39424d95c115b738a8bc6f1a83/src/app.module.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">Module</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ConfigModule</span><span class="p">,</span> <span class="nx">ConfigService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/config</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">TypeOrmModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AppController</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./app.controller</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AppService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./app.service</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">dbConfig</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./common/config/db.config</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">validate</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./common/config/env.validation</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Module</span><span class="p">({</span>
<span class="na">imports</span><span class="p">:</span> <span class="p">[</span>
<span class="nx">ConfigModule</span><span class="p">.</span><span class="nx">forRoot</span><span class="p">({</span>
<span class="na">isGlobal</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">load</span><span class="p">:</span> <span class="p">[</span><span class="nx">dbConfig</span><span class="p">],</span>
<span class="nx">validate</span><span class="p">,</span>
<span class="p">}),</span>
<span class="nx">TypeOrmModule</span><span class="p">.</span><span class="nx">forRootAsync</span><span class="p">({</span>
<span class="na">imports</span><span class="p">:</span> <span class="p">[</span><span class="nx">ConfigModule</span><span class="p">],</span>
<span class="na">useFactory</span><span class="p">:</span> <span class="k">async</span> <span class="p">(</span><span class="na">configService</span><span class="p">:</span> <span class="nx">ConfigService</span><span class="p">)</span> <span class="o">=></span> <span class="p">({...</span><span class="nx">configService</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">database</span><span class="dl">'</span><span class="p">)}),</span>
<span class="na">inject</span><span class="p">:</span> <span class="p">[</span><span class="nx">ConfigService</span><span class="p">],</span>
<span class="p">})</span>
<span class="p">],</span>
<span class="na">controllers</span><span class="p">:</span> <span class="p">[</span><span class="nx">AppController</span><span class="p">],</span>
<span class="na">providers</span><span class="p">:</span> <span class="p">[</span><span class="nx">AppService</span><span class="p">],</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">AppModule</span> <span class="p">{}</span></code></pre></figure>
<p>Create an ormconfig.ts file in the main application root:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/12c7a3e750027c39424d95c115b738a8bc6f1a83/ormconfig.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
ormconfig.ts <a href="https://github.com/imuchene/storefront-backend/blob/12c7a3e750027c39424d95c115b738a8bc6f1a83/ormconfig.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/12c7a3e750027c39424d95c115b738a8bc6f1a83/ormconfig.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">ConfigModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@nestjs/config</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">dbConfig</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./src/common/config/db.config</span><span class="dl">"</span><span class="p">;</span>
<span class="nx">ConfigModule</span><span class="p">.</span><span class="nx">forRoot</span><span class="p">({</span>
<span class="na">load</span><span class="p">:</span> <span class="p">[</span><span class="nx">dbConfig</span><span class="p">]</span>
<span class="p">})</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">dbConfig</span><span class="p">()</span></code></pre></figure>
<p>Create a db.config file like so:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/02f390a3f49b49c32fb68f0c82953d746613655e/src/common/config/db.config.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/common/config/db.config.ts <a href="https://github.com/imuchene/storefront-backend/blob/02f390a3f49b49c32fb68f0c82953d746613655e/src/common/config/db.config.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/02f390a3f49b49c32fb68f0c82953d746613655e/src/common/config/db.config.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">registerAs</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@nestjs/config</span><span class="dl">"</span><span class="p">;</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">registerAs</span><span class="p">(</span><span class="dl">'</span><span class="s1">database</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">postgres</span><span class="dl">'</span><span class="p">,</span>
<span class="na">logging</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">TYPEORM_LOGGING</span><span class="p">,</span>
<span class="na">host</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">DB_MAIN_HOST</span><span class="p">,</span>
<span class="na">port</span><span class="p">:</span> <span class="nb">parseInt</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">DB_MAIN_PORT</span><span class="p">),</span>
<span class="na">username</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">DB_MAIN_USER</span><span class="p">,</span>
<span class="na">password</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">DB_MAIN_PASSWORD</span><span class="p">,</span>
<span class="na">database</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">DB_MAIN_DATABASE</span><span class="p">,</span>
<span class="na">autoLoadEntities</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">ssl</span><span class="p">:</span> <span class="p">{</span>
<span class="na">rejectUnauthorized</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="p">},</span>
<span class="na">entities</span><span class="p">:</span> <span class="p">[</span>
<span class="nx">baseFolder</span><span class="p">()</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">modules/**/*.entity{.ts,.js}</span><span class="dl">'</span><span class="p">,</span>
<span class="nx">baseFolder</span><span class="p">()</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">modules/**/*.view{.ts,.js}</span><span class="dl">'</span><span class="p">,</span>
<span class="p">],</span>
<span class="na">migrations</span><span class="p">:</span> <span class="p">[</span><span class="nx">baseFolder</span><span class="p">()</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">migrations/**/*{.ts,.js}</span><span class="dl">'</span><span class="p">],</span>
<span class="na">cli</span><span class="p">:</span> <span class="p">{</span>
<span class="na">migrationsDir</span><span class="p">:</span> <span class="nx">baseFolder</span><span class="p">()</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">/migrations</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="kd">function</span> <span class="nx">baseFolder</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">regex</span> <span class="o">=</span> <span class="sr">/common+</span><span class="se">(\/</span><span class="sr">|</span><span class="se">\\)</span><span class="sr">+config/gi</span><span class="p">;</span>
<span class="k">return</span> <span class="nx">__dirname</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="nx">regex</span><span class="p">,</span> <span class="dl">''</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<p>Create an env.validation class to validate environment variables:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/12c7a3e750027c39424d95c115b738a8bc6f1a83/src/common/config/env.validation.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/common/config/env.validation.ts <a href="https://github.com/imuchene/storefront-backend/blob/12c7a3e750027c39424d95c115b738a8bc6f1a83/src/common/config/env.validation.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/12c7a3e750027c39424d95c115b738a8bc6f1a83/src/common/config/env.validation.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">plainToClass</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">class-transformer</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">IsBoolean</span><span class="p">,</span> <span class="nx">IsEnum</span><span class="p">,</span> <span class="nx">IsNumber</span><span class="p">,</span> <span class="nx">IsString</span><span class="p">,</span> <span class="nx">validateSync</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">class-validator</span><span class="dl">'</span><span class="p">;</span>
<span class="kr">enum</span> <span class="nx">Environment</span> <span class="p">{</span>
<span class="nx">Development</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">development</span><span class="dl">"</span><span class="p">,</span>
<span class="nx">Production</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">production</span><span class="dl">"</span><span class="p">,</span>
<span class="nx">Test</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">test</span><span class="dl">"</span><span class="p">,</span>
<span class="nx">Provision</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">provision</span><span class="dl">"</span><span class="p">,</span>
<span class="p">}</span>
<span class="kd">class</span> <span class="nx">EnvironmentVariables</span> <span class="p">{</span>
<span class="p">@</span><span class="nd">IsEnum</span><span class="p">(</span><span class="nx">Environment</span><span class="p">)</span>
<span class="nx">NODE_ENV</span><span class="p">:</span> <span class="nx">Environment</span><span class="p">;</span>
<span class="p">@</span><span class="nd">IsNumber</span><span class="p">()</span>
<span class="nx">PORT</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">@</span><span class="nd">IsString</span><span class="p">()</span>
<span class="nx">DB_MAIN_HOST</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">IsNumber</span><span class="p">()</span>
<span class="nx">DB_MAIN_PORT</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">@</span><span class="nd">IsString</span><span class="p">()</span>
<span class="nx">DB_MAIN_USER</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">IsString</span><span class="p">()</span>
<span class="nx">DB_MAIN_PASSWORD</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">IsString</span><span class="p">()</span>
<span class="nx">DB_MAIN_DATABASE</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">@</span><span class="nd">IsBoolean</span><span class="p">()</span>
<span class="nx">TYPEORM_LOGGING</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nx">validate</span><span class="p">(</span><span class="nx">config</span><span class="p">:</span> <span class="nb">Record</span><span class="o"><</span><span class="kr">string</span><span class="p">,</span> <span class="nx">unknown</span><span class="o">></span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">validatedConfig</span> <span class="o">=</span> <span class="nx">plainToClass</span><span class="p">(</span>
<span class="nx">EnvironmentVariables</span><span class="p">,</span>
<span class="nx">config</span><span class="p">,</span>
<span class="p">{</span> <span class="na">enableImplicitConversion</span><span class="p">:</span> <span class="kc">true</span> <span class="p">},</span>
<span class="p">);</span>
<span class="kd">const</span> <span class="nx">errors</span> <span class="o">=</span> <span class="nx">validateSync</span><span class="p">(</span><span class="nx">validatedConfig</span><span class="p">,</span> <span class="p">{</span> <span class="na">skipMissingProperties</span><span class="p">:</span> <span class="kc">false</span> <span class="p">});</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">errors</span><span class="p">.</span><span class="nx">length</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="nx">errors</span><span class="p">.</span><span class="nx">toString</span><span class="p">());</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">validatedConfig</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>Lastly, create migration tasks in your package.json:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/02f390a3f49b49c32fb68f0c82953d746613655e/package.json">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
package.json <a href="https://github.com/imuchene/storefront-backend/blob/02f390a3f49b49c32fb68f0c82953d746613655e/package.json">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/02f390a3f49b49c32fb68f0c82953d746613655e/package.json">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">{</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"storefront-backend"</span><span class="p">,</span><span class="w">
</span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.0.1"</span><span class="p">,</span><span class="w">
</span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
</span><span class="nl">"author"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
</span><span class="nl">"private"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="nl">"license"</span><span class="p">:</span><span class="w"> </span><span class="s2">"UNLICENSED"</span><span class="p">,</span><span class="w">
</span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"prebuild"</span><span class="p">:</span><span class="w"> </span><span class="s2">"rimraf dist"</span><span class="p">,</span><span class="w">
</span><span class="nl">"build"</span><span class="p">:</span><span class="w"> </span><span class="s2">"nest build"</span><span class="p">,</span><span class="w">
</span><span class="nl">"format"</span><span class="p">:</span><span class="w"> </span><span class="s2">"prettier --write </span><span class="se">\"</span><span class="s2">src/**/*.ts</span><span class="se">\"</span><span class="s2"> </span><span class="se">\"</span><span class="s2">test/**/*.ts</span><span class="se">\"</span><span class="s2">"</span><span class="p">,</span><span class="w">
</span><span class="nl">"start"</span><span class="p">:</span><span class="w"> </span><span class="s2">"nest start"</span><span class="p">,</span><span class="w">
</span><span class="nl">"start:dev"</span><span class="p">:</span><span class="w"> </span><span class="s2">"nest start --watch"</span><span class="p">,</span><span class="w">
</span><span class="nl">"start:debug"</span><span class="p">:</span><span class="w"> </span><span class="s2">"nest start --debug --watch"</span><span class="p">,</span><span class="w">
</span><span class="nl">"start:prod"</span><span class="p">:</span><span class="w"> </span><span class="s2">"node dist/main"</span><span class="p">,</span><span class="w">
</span><span class="nl">"lint"</span><span class="p">:</span><span class="w"> </span><span class="s2">"eslint </span><span class="se">\"</span><span class="s2">{src,apps,libs,test}/**/*.ts</span><span class="se">\"</span><span class="s2"> --fix"</span><span class="p">,</span><span class="w">
</span><span class="nl">"test"</span><span class="p">:</span><span class="w"> </span><span class="s2">"jest"</span><span class="p">,</span><span class="w">
</span><span class="nl">"test:watch"</span><span class="p">:</span><span class="w"> </span><span class="s2">"jest --watch"</span><span class="p">,</span><span class="w">
</span><span class="nl">"test:cov"</span><span class="p">:</span><span class="w"> </span><span class="s2">"jest --coverage"</span><span class="p">,</span><span class="w">
</span><span class="nl">"test:debug"</span><span class="p">:</span><span class="w"> </span><span class="s2">"node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand"</span><span class="p">,</span><span class="w">
</span><span class="nl">"test:e2e"</span><span class="p">:</span><span class="w"> </span><span class="s2">"jest --config ./test/jest-e2e.json"</span><span class="p">,</span><span class="w">
</span><span class="nl">"typeorm"</span><span class="p">:</span><span class="w"> </span><span class="s2">"node --require ts-node/register ./node_modules/typeorm/cli.js"</span><span class="p">,</span><span class="w">
</span><span class="nl">"entity:create"</span><span class="p">:</span><span class="w"> </span><span class="s2">"typeorm entity:create -n"</span><span class="p">,</span><span class="w">
</span><span class="nl">"migrations:create"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm run typeorm -- migration:create -n"</span><span class="p">,</span><span class="w">
</span><span class="nl">"migrations:run"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm run typeorm -- migration:run"</span><span class="p">,</span><span class="w">
</span><span class="nl">"migrations:revert"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm run typeorm -- migration:revert"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"dependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"@nestjs/common"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^8.4.3"</span><span class="p">,</span><span class="w">
</span><span class="nl">"@nestjs/config"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^2.0.0"</span><span class="p">,</span><span class="w">
</span><span class="nl">"@nestjs/core"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^8.4.3"</span><span class="p">,</span></code></pre></figure>
<p>To create a new migration, we’ll have to enter on our terminal:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm run migrations:create MigrationName
</code></pre></div></div>
<p>To run our migrations (once we’ve created them), all we need to do is enter the following on our terminal:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm run migrations:run
</code></pre></div></div>
<p>To reverse a migration (by default the previously run migration):</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm run migrations:revert
</code></pre></div></div>
<p>Optionally, you can use the configservice on the main application entry point (main.ts) to hide the default application port:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/12c7a3e750027c39424d95c115b738a8bc6f1a83/src/main.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/main.ts <a href="https://github.com/imuchene/storefront-backend/blob/12c7a3e750027c39424d95c115b738a8bc6f1a83/src/main.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/12c7a3e750027c39424d95c115b738a8bc6f1a83/src/main.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span> <span class="nx">ConfigService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/config</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">NestFactory</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/core</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AppModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./app.module</span><span class="dl">'</span><span class="p">;</span>
<span class="k">async</span> <span class="kd">function</span> <span class="nx">bootstrap</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">NestFactory</span><span class="p">.</span><span class="nx">create</span><span class="p">(</span><span class="nx">AppModule</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">configService</span> <span class="o">=</span> <span class="nx">app</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">ConfigService</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">port</span> <span class="o">=</span> <span class="nx">configService</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">PORT</span><span class="dl">'</span><span class="p">);</span>
<span class="k">await</span> <span class="nx">app</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="nx">port</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">bootstrap</span><span class="p">();</span></code></pre></figure>
<p>Create a <strong>.env</strong> file at the application root. Mine looks like so:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">NODE_ENV</span><span class="o">=</span>development
<span class="nv">PORT</span><span class="o">=</span>3000
<span class="c"># Database Configuration</span>
<span class="nv">DB_MAIN_HOST</span><span class="o">=</span>localhost
<span class="nv">DB_MAIN_PORT</span><span class="o">=</span>5432
<span class="nv">DB_MAIN_USER</span><span class="o">=</span>storefront
<span class="nv">DB_MAIN_PASSWORD</span><span class="o">=</span>your_database_password
<span class="nv">DB_MAIN_DATABASE</span><span class="o">=</span>storefront
<span class="nv">TYPEORM_LOGGING</span><span class="o">=</span><span class="nb">true</span>
</code></pre></div></div>
<p>You should now be able to start the application successfully.</p>
<h3 id="create-the-migrations">Create the migrations</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm run migrations:create CreateProducts
</code></pre></div></div>
<p>Fill in the products migration:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/02f390a3f49b49c32fb68f0c82953d746613655e/src/migrations/1648820162516-CreateProducts.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/migrations/1648820162516-CreateProducts.ts <a href="https://github.com/imuchene/storefront-backend/blob/02f390a3f49b49c32fb68f0c82953d746613655e/src/migrations/1648820162516-CreateProducts.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/02f390a3f49b49c32fb68f0c82953d746613655e/src/migrations/1648820162516-CreateProducts.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span><span class="nx">MigrationInterface</span><span class="p">,</span> <span class="nx">QueryRunner</span><span class="p">,</span> <span class="nx">Table</span><span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">typeorm</span><span class="dl">"</span><span class="p">;</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">CreateProducts1648820162516</span> <span class="k">implements</span> <span class="nx">MigrationInterface</span> <span class="p">{</span>
<span class="k">public</span> <span class="k">async</span> <span class="nx">up</span><span class="p">(</span><span class="nx">queryRunner</span><span class="p">:</span> <span class="nx">QueryRunner</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="k">void</span><span class="o">></span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">queryRunner</span><span class="p">.</span><span class="nx">createTable</span><span class="p">(</span><span class="k">new</span> <span class="nx">Table</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">products</span><span class="dl">'</span><span class="p">,</span>
<span class="na">columns</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">id</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">uuid</span><span class="dl">'</span><span class="p">,</span>
<span class="na">isPrimary</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">isGenerated</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">default</span><span class="p">:</span> <span class="dl">'</span><span class="s1">uuid_generate_v4()</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">name</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">varchar</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">unit_price</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">decimal(12,2)</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">is_discontinued</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">boolean</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">description</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">text</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">image</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">varchar</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">created_at</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">timestamptz</span><span class="dl">'</span><span class="p">,</span>
<span class="na">default</span><span class="p">:</span> <span class="dl">'</span><span class="s1">now()</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">updated_at</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">timestamptz</span><span class="dl">'</span><span class="p">,</span>
<span class="na">default</span><span class="p">:</span> <span class="dl">'</span><span class="s1">now()</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">]</span>
<span class="p">}));</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">async</span> <span class="nx">down</span><span class="p">(</span><span class="nx">queryRunner</span><span class="p">:</span> <span class="nx">QueryRunner</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="k">void</span><span class="o">></span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">queryRunner</span><span class="p">.</span><span class="nx">dropTable</span><span class="p">(</span><span class="dl">'</span><span class="s1">products</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm run migrations:create CreateOrders
</code></pre></div></div>
<p>Fill in the orders migration:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/02f390a3f49b49c32fb68f0c82953d746613655e/src/migrations/1648820154148-CreateOrders.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/migrations/1648820154148-CreateOrders.ts <a href="https://github.com/imuchene/storefront-backend/blob/02f390a3f49b49c32fb68f0c82953d746613655e/src/migrations/1648820154148-CreateOrders.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/02f390a3f49b49c32fb68f0c82953d746613655e/src/migrations/1648820154148-CreateOrders.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span><span class="nx">MigrationInterface</span><span class="p">,</span> <span class="nx">QueryRunner</span><span class="p">,</span> <span class="nx">Table</span><span class="p">,</span> <span class="nx">TableForeignKey</span><span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">typeorm</span><span class="dl">"</span><span class="p">;</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">CreateOrders1648820154148</span> <span class="k">implements</span> <span class="nx">MigrationInterface</span> <span class="p">{</span>
<span class="k">public</span> <span class="k">async</span> <span class="nx">up</span><span class="p">(</span><span class="nx">queryRunner</span><span class="p">:</span> <span class="nx">QueryRunner</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="k">void</span><span class="o">></span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">queryRunner</span><span class="p">.</span><span class="nx">createTable</span><span class="p">(</span><span class="k">new</span> <span class="nx">Table</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">orders</span><span class="dl">'</span><span class="p">,</span>
<span class="na">columns</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">id</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">uuid</span><span class="dl">'</span><span class="p">,</span>
<span class="na">isPrimary</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">isGenerated</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">default</span><span class="p">:</span> <span class="dl">'</span><span class="s1">uuid_generate_v4()</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">customer_id</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">uuid</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">total_amount</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">decimal(12,2)</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">created_at</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">timestamptz</span><span class="dl">'</span><span class="p">,</span>
<span class="na">default</span><span class="p">:</span> <span class="dl">'</span><span class="s1">now()</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">updated_at</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">timestamptz</span><span class="dl">'</span><span class="p">,</span>
<span class="na">default</span><span class="p">:</span> <span class="dl">'</span><span class="s1">now()</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">]</span>
<span class="p">}));</span>
<span class="k">await</span> <span class="nx">queryRunner</span><span class="p">.</span><span class="nx">createForeignKey</span><span class="p">(</span>
<span class="dl">'</span><span class="s1">orders</span><span class="dl">'</span><span class="p">,</span>
<span class="k">new</span> <span class="nx">TableForeignKey</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">customer_id_fk</span><span class="dl">'</span><span class="p">,</span>
<span class="na">columnNames</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">customer_id</span><span class="dl">'</span><span class="p">],</span>
<span class="na">referencedColumnNames</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">id</span><span class="dl">'</span><span class="p">],</span>
<span class="na">referencedTableName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">customers</span><span class="dl">'</span><span class="p">,</span>
<span class="p">}),</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">async</span> <span class="nx">down</span><span class="p">(</span><span class="nx">queryRunner</span><span class="p">:</span> <span class="nx">QueryRunner</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="k">void</span><span class="o">></span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">queryRunner</span><span class="p">.</span><span class="nx">dropForeignKey</span><span class="p">(</span>
<span class="dl">'</span><span class="s1">orders</span><span class="dl">'</span><span class="p">,</span>
<span class="k">new</span> <span class="nx">TableForeignKey</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">customer_id_fk</span><span class="dl">'</span><span class="p">,</span>
<span class="na">columnNames</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">customer_id</span><span class="dl">'</span><span class="p">],</span>
<span class="na">referencedColumnNames</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">id</span><span class="dl">'</span><span class="p">],</span>
<span class="na">referencedTableName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">customers</span><span class="dl">'</span><span class="p">,</span>
<span class="p">}),</span>
<span class="p">);</span>
<span class="k">await</span> <span class="nx">queryRunner</span><span class="p">.</span><span class="nx">dropTable</span><span class="p">(</span><span class="dl">'</span><span class="s1">orders</span><span class="dl">'</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm run migrations:create CreateCustomers
</code></pre></div></div>
<p>Fill in the customers migration:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/02f390a3f49b49c32fb68f0c82953d746613655e/src/migrations/1648820125567-CreateCustomers.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/migrations/1648820125567-CreateCustomers.ts <a href="https://github.com/imuchene/storefront-backend/blob/02f390a3f49b49c32fb68f0c82953d746613655e/src/migrations/1648820125567-CreateCustomers.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/02f390a3f49b49c32fb68f0c82953d746613655e/src/migrations/1648820125567-CreateCustomers.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span><span class="nx">MigrationInterface</span><span class="p">,</span> <span class="nx">QueryRunner</span><span class="p">,</span> <span class="nx">Table</span><span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">typeorm</span><span class="dl">"</span><span class="p">;</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">CreateCustomers1648820125567</span> <span class="k">implements</span> <span class="nx">MigrationInterface</span> <span class="p">{</span>
<span class="k">public</span> <span class="k">async</span> <span class="nx">up</span><span class="p">(</span><span class="nx">queryRunner</span><span class="p">:</span> <span class="nx">QueryRunner</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="k">void</span><span class="o">></span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">queryRunner</span><span class="p">.</span><span class="nx">createTable</span><span class="p">(</span><span class="k">new</span> <span class="nx">Table</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">customers</span><span class="dl">'</span><span class="p">,</span>
<span class="na">columns</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">id</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">uuid</span><span class="dl">'</span><span class="p">,</span>
<span class="na">isPrimary</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">isGenerated</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">default</span><span class="p">:</span> <span class="dl">'</span><span class="s1">uuid_generate_v4()</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">name</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">varchar</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">email</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">varchar</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">phone_number</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">varchar</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">password_digest</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">varchar</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">created_at</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">timestamptz</span><span class="dl">'</span><span class="p">,</span>
<span class="na">default</span><span class="p">:</span> <span class="dl">'</span><span class="s1">now()</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">updated_at</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">timestamptz</span><span class="dl">'</span><span class="p">,</span>
<span class="na">default</span><span class="p">:</span> <span class="dl">'</span><span class="s1">now()</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">]</span>
<span class="p">}));</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">async</span> <span class="nx">down</span><span class="p">(</span><span class="nx">queryRunner</span><span class="p">:</span> <span class="nx">QueryRunner</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="k">void</span><span class="o">></span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">queryRunner</span><span class="p">.</span><span class="nx">dropTable</span><span class="p">(</span><span class="dl">'</span><span class="s1">customers</span><span class="dl">'</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm run migrations:create CreateOrderItems
</code></pre></div></div>
<p>Fill in the order items migration:</p>
<div class="github-sample-reference">
<div class="author-info">
<a href="https://github.com/imuchene/storefront-backend/blob/2b9c99323e613762255ed4d83d6442eace1d6a23/src/migrations/1649079742594-CreateOrderItems.ts">This Github Sample</a> is by <a href="https://github.com/imuchene">imuchene</a>
</div>
<div class="meta-info">
src/migrations/1649079742594-CreateOrderItems.ts <a href="https://github.com/imuchene/storefront-backend/blob/2b9c99323e613762255ed4d83d6442eace1d6a23/src/migrations/1649079742594-CreateOrderItems.ts">view</a> <a href="https://raw.githubusercontent.com/imuchene/storefront-backend/2b9c99323e613762255ed4d83d6442eace1d6a23/src/migrations/1649079742594-CreateOrderItems.ts">raw</a>
</div>
</div>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">import</span> <span class="p">{</span>
<span class="nx">MigrationInterface</span><span class="p">,</span>
<span class="nx">QueryRunner</span><span class="p">,</span>
<span class="nx">Table</span><span class="p">,</span>
<span class="nx">TableForeignKey</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">typeorm</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">CreateOrderItems1649079742594</span> <span class="k">implements</span> <span class="nx">MigrationInterface</span> <span class="p">{</span>
<span class="k">public</span> <span class="k">async</span> <span class="nx">up</span><span class="p">(</span><span class="nx">queryRunner</span><span class="p">:</span> <span class="nx">QueryRunner</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="k">void</span><span class="o">></span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">queryRunner</span><span class="p">.</span><span class="nx">createTable</span><span class="p">(</span>
<span class="k">new</span> <span class="nx">Table</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">order_items</span><span class="dl">'</span><span class="p">,</span>
<span class="na">columns</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">order_id</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">uuid</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">product_id</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">uuid</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">unit_price</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">decimal(12,2)</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">quantity</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">integer</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">],</span>
<span class="p">}),</span>
<span class="p">);</span>
<span class="k">await</span> <span class="nx">queryRunner</span><span class="p">.</span><span class="nx">createForeignKeys</span><span class="p">(</span><span class="dl">'</span><span class="s1">order_items</span><span class="dl">'</span><span class="p">,</span> <span class="p">[</span>
<span class="k">new</span> <span class="nx">TableForeignKey</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">order_id_fk</span><span class="dl">'</span><span class="p">,</span>
<span class="na">columnNames</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">order_id</span><span class="dl">'</span><span class="p">],</span>
<span class="na">referencedColumnNames</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">id</span><span class="dl">'</span><span class="p">],</span>
<span class="na">referencedTableName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">orders</span><span class="dl">'</span><span class="p">,</span>
<span class="p">}),</span>
<span class="k">new</span> <span class="nx">TableForeignKey</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">product_id_fk</span><span class="dl">'</span><span class="p">,</span>
<span class="na">columnNames</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">product_id</span><span class="dl">'</span><span class="p">],</span>
<span class="na">referencedColumnNames</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">id</span><span class="dl">'</span><span class="p">],</span>
<span class="na">referencedTableName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">products</span><span class="dl">'</span><span class="p">,</span>
<span class="p">}),</span>
<span class="p">]);</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">async</span> <span class="nx">down</span><span class="p">(</span><span class="nx">queryRunner</span><span class="p">:</span> <span class="nx">QueryRunner</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="k">void</span><span class="o">></span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">queryRunner</span><span class="p">.</span><span class="nx">dropForeignKeys</span><span class="p">(</span><span class="dl">'</span><span class="s1">order_items</span><span class="dl">'</span><span class="p">,</span> <span class="p">[</span>
<span class="k">new</span> <span class="nx">TableForeignKey</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">order_id_fk</span><span class="dl">'</span><span class="p">,</span>
<span class="na">columnNames</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">order_id</span><span class="dl">'</span><span class="p">],</span>
<span class="na">referencedColumnNames</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">id</span><span class="dl">'</span><span class="p">],</span>
<span class="na">referencedTableName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">orders</span><span class="dl">'</span><span class="p">,</span>
<span class="p">}),</span>
<span class="k">new</span> <span class="nx">TableForeignKey</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">product_id_fk</span><span class="dl">'</span><span class="p">,</span>
<span class="na">columnNames</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">product_id</span><span class="dl">'</span><span class="p">],</span>
<span class="na">referencedColumnNames</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">id</span><span class="dl">'</span><span class="p">],</span>
<span class="na">referencedTableName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">products</span><span class="dl">'</span><span class="p">,</span>
<span class="p">}),</span>
<span class="p">]);</span>
<span class="k">await</span> <span class="nx">queryRunner</span><span class="p">.</span><span class="nx">dropTable</span><span class="p">(</span><span class="dl">'</span><span class="s1">order_items</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<h3 id="run-the-migrations">Run the Migrations</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm run migrations:run
</code></pre></div></div>
<p>With TypeORM logging enabled, you should see a very long log printed on the screen:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="o">></span> storefront-backend@0.0.1 migrations:run
<span class="o">></span> npm run typeorm <span class="nt">--</span> migration:run
<span class="o">></span> storefront-backend@0.0.1 typeorm
<span class="o">></span> node <span class="nt">--require</span> ts-node/register ./node_modules/typeorm/cli.js <span class="s2">"migration:run"</span>
query: SELECT <span class="k">*</span> FROM current_schema<span class="o">()</span>
query: SHOW server_version<span class="p">;</span>
query: SELECT <span class="k">*</span> FROM <span class="s2">"information_schema"</span>.<span class="s2">"tables"</span> WHERE <span class="s2">"table_schema"</span> <span class="o">=</span> <span class="s1">'public'</span> AND <span class="s2">"table_name"</span> <span class="o">=</span> <span class="s1">'migrations'</span>
query: SELECT <span class="k">*</span> FROM <span class="s2">"information_schema"</span>.<span class="s2">"tables"</span> WHERE <span class="s2">"table_schema"</span> <span class="o">=</span> <span class="s1">'public'</span> AND <span class="s2">"table_name"</span> <span class="o">=</span> <span class="s1">'typeorm_metadata'</span>
query: SELECT <span class="k">*</span> FROM <span class="s2">"migrations"</span> <span class="s2">"migrations"</span> ORDER BY <span class="s2">"id"</span> DESC
0 migrations are already loaded <span class="k">in </span>the database.
4 migrations were found <span class="k">in </span>the <span class="nb">source </span>code.
4 migrations are new migrations that needs to be executed.
query: START TRANSACTION
query: CREATE TABLE <span class="s2">"customers"</span> <span class="o">(</span><span class="s2">"id"</span> uuid NOT NULL DEFAULT uuid_generate_v4<span class="o">()</span>, <span class="s2">"name"</span> varchar NOT NULL, <span class="s2">"email"</span> varchar NOT NULL, <span class="s2">"phone_number"</span> varchar NOT NULL, <span class="s2">"password_digest"</span> varchar NOT NULL, <span class="s2">"created_at"</span> timestamptz NOT NULL DEFAULT now<span class="o">()</span>, <span class="s2">"updated_at"</span> timestamptz NOT NULL DEFAULT now<span class="o">()</span>, CONSTRAINT <span class="s2">"PK_133ec679a801fab5e070f73d3ea"</span> PRIMARY KEY <span class="o">(</span><span class="s2">"id"</span><span class="o">))</span>
query: INSERT INTO <span class="s2">"migrations"</span><span class="o">(</span><span class="s2">"timestamp"</span>, <span class="s2">"name"</span><span class="o">)</span> VALUES <span class="o">(</span><span class="nv">$1</span>, <span class="nv">$2</span><span class="o">)</span> <span class="nt">--</span> PARAMETERS: <span class="o">[</span>1648820125567,<span class="s2">"CreateCustomers1648820125567"</span><span class="o">]</span>
Migration CreateCustomers1648820125567 has been executed successfully.
query: CREATE TABLE <span class="s2">"orders"</span> <span class="o">(</span><span class="s2">"id"</span> uuid NOT NULL DEFAULT uuid_generate_v4<span class="o">()</span>, <span class="s2">"customer_id"</span> uuid NOT NULL, <span class="s2">"total_amount"</span> decimal<span class="o">(</span>12,2<span class="o">)</span> NOT NULL, <span class="s2">"created_at"</span> timestamptz NOT NULL DEFAULT now<span class="o">()</span>, <span class="s2">"updated_at"</span> timestamptz NOT NULL DEFAULT now<span class="o">()</span>, CONSTRAINT <span class="s2">"PK_710e2d4957aa5878dfe94e4ac2f"</span> PRIMARY KEY <span class="o">(</span><span class="s2">"id"</span><span class="o">))</span>
query: SELECT <span class="k">*</span> FROM current_schema<span class="o">()</span>
query: SELECT <span class="k">*</span> FROM current_database<span class="o">()</span>
query: SELECT <span class="s2">"table_schema"</span>, <span class="s2">"table_name"</span> FROM <span class="s2">"information_schema"</span>.<span class="s2">"tables"</span> WHERE <span class="o">(</span><span class="s2">"table_schema"</span> <span class="o">=</span> <span class="s1">'public'</span> AND <span class="s2">"table_name"</span> <span class="o">=</span> <span class="s1">'orders'</span><span class="o">)</span>
query: SELECT TRUE FROM information_schema.columns WHERE table_name <span class="o">=</span> <span class="s1">'pg_class'</span> and column_name <span class="o">=</span> <span class="s1">'relispartition'</span>
query: SELECT columns.<span class="k">*</span>, pg_catalog.col_description<span class="o">((</span><span class="s1">'"'</span> <span class="o">||</span> table_catalog <span class="o">||</span> <span class="s1">'"."'</span> <span class="o">||</span> table_schema <span class="o">||</span> <span class="s1">'"."'</span> <span class="o">||</span> table_name <span class="o">||</span> <span class="s1">'"'</span><span class="o">)</span>::regclass::oid, ordinal_position<span class="o">)</span> AS description, <span class="o">(</span><span class="s1">'"'</span> <span class="o">||</span> <span class="s2">"udt_schema"</span> <span class="o">||</span> <span class="s1">'"."'</span> <span class="o">||</span> <span class="s2">"udt_name"</span> <span class="o">||</span> <span class="s1">'"'</span><span class="o">)</span>::<span class="s2">"regtype"</span> AS <span class="s2">"regtype"</span>, pg_catalog.format_type<span class="o">(</span><span class="s2">"col_attr"</span>.<span class="s2">"atttypid"</span>, <span class="s2">"col_attr"</span>.<span class="s2">"atttypmod"</span><span class="o">)</span> AS <span class="s2">"format_type"</span> FROM <span class="s2">"information_schema"</span>.<span class="s2">"columns"</span> LEFT JOIN <span class="s2">"pg_catalog"</span>.<span class="s2">"pg_attribute"</span> AS <span class="s2">"col_attr"</span> ON <span class="s2">"col_attr"</span>.<span class="s2">"attname"</span> <span class="o">=</span> <span class="s2">"columns"</span>.<span class="s2">"column_name"</span> AND <span class="s2">"col_attr"</span>.<span class="s2">"attrelid"</span> <span class="o">=</span> <span class="o">(</span> SELECT <span class="s2">"cls"</span>.<span class="s2">"oid"</span> FROM <span class="s2">"pg_catalog"</span>.<span class="s2">"pg_class"</span> AS <span class="s2">"cls"</span> LEFT JOIN <span class="s2">"pg_catalog"</span>.<span class="s2">"pg_namespace"</span> AS <span class="s2">"ns"</span> ON <span class="s2">"ns"</span>.<span class="s2">"oid"</span> <span class="o">=</span> <span class="s2">"cls"</span>.<span class="s2">"relnamespace"</span> WHERE <span class="s2">"cls"</span>.<span class="s2">"relname"</span> <span class="o">=</span> <span class="s2">"columns"</span>.<span class="s2">"table_name"</span> AND <span class="s2">"ns"</span>.<span class="s2">"nspname"</span> <span class="o">=</span> <span class="s2">"columns"</span>.<span class="s2">"table_schema"</span> <span class="o">)</span> WHERE <span class="o">(</span><span class="s2">"table_schema"</span> <span class="o">=</span> <span class="s1">'public'</span> AND <span class="s2">"table_name"</span> <span class="o">=</span> <span class="s1">'orders'</span><span class="o">)</span>
query: SELECT <span class="s2">"ns"</span>.<span class="s2">"nspname"</span> AS <span class="s2">"table_schema"</span>, <span class="s2">"t"</span>.<span class="s2">"relname"</span> AS <span class="s2">"table_name"</span>, <span class="s2">"cnst"</span>.<span class="s2">"conname"</span> AS <span class="s2">"constraint_name"</span>, pg_get_constraintdef<span class="o">(</span><span class="s2">"cnst"</span>.<span class="s2">"oid"</span><span class="o">)</span> AS <span class="s2">"expression"</span>, CASE <span class="s2">"cnst"</span>.<span class="s2">"contype"</span> WHEN <span class="s1">'p'</span> THEN <span class="s1">'PRIMARY'</span> WHEN <span class="s1">'u'</span> THEN <span class="s1">'UNIQUE'</span> WHEN <span class="s1">'c'</span> THEN <span class="s1">'CHECK'</span> WHEN <span class="s1">'x'</span> THEN <span class="s1">'EXCLUDE'</span> END AS <span class="s2">"constraint_type"</span>, <span class="s2">"a"</span>.<span class="s2">"attname"</span> AS <span class="s2">"column_name"</span> FROM <span class="s2">"pg_constraint"</span> <span class="s2">"cnst"</span> INNER JOIN <span class="s2">"pg_class"</span> <span class="s2">"t"</span> ON <span class="s2">"t"</span>.<span class="s2">"oid"</span> <span class="o">=</span> <span class="s2">"cnst"</span>.<span class="s2">"conrelid"</span> INNER JOIN <span class="s2">"pg_namespace"</span> <span class="s2">"ns"</span> ON <span class="s2">"ns"</span>.<span class="s2">"oid"</span> <span class="o">=</span> <span class="s2">"cnst"</span>.<span class="s2">"connamespace"</span> LEFT JOIN <span class="s2">"pg_attribute"</span> <span class="s2">"a"</span> ON <span class="s2">"a"</span>.<span class="s2">"attrelid"</span> <span class="o">=</span> <span class="s2">"cnst"</span>.<span class="s2">"conrelid"</span> AND <span class="s2">"a"</span>.<span class="s2">"attnum"</span> <span class="o">=</span> ANY <span class="o">(</span><span class="s2">"cnst"</span>.<span class="s2">"conkey"</span><span class="o">)</span> WHERE <span class="s2">"t"</span>.<span class="s2">"relkind"</span> IN <span class="o">(</span><span class="s1">'r'</span>, <span class="s1">'p'</span><span class="o">)</span> AND <span class="o">((</span><span class="s2">"ns"</span>.<span class="s2">"nspname"</span> <span class="o">=</span> <span class="s1">'public'</span> AND <span class="s2">"t"</span>.<span class="s2">"relname"</span> <span class="o">=</span> <span class="s1">'orders'</span><span class="o">))</span>
query: SELECT <span class="s2">"ns"</span>.<span class="s2">"nspname"</span> AS <span class="s2">"table_schema"</span>, <span class="s2">"t"</span>.<span class="s2">"relname"</span> AS <span class="s2">"table_name"</span>, <span class="s2">"i"</span>.<span class="s2">"relname"</span> AS <span class="s2">"constraint_name"</span>, <span class="s2">"a"</span>.<span class="s2">"attname"</span> AS <span class="s2">"column_name"</span>, CASE <span class="s2">"ix"</span>.<span class="s2">"indisunique"</span> WHEN <span class="s1">'t'</span> THEN <span class="s1">'TRUE'</span> ELSE<span class="s1">'FALSE'</span> END AS <span class="s2">"is_unique"</span>, pg_get_expr<span class="o">(</span><span class="s2">"ix"</span>.<span class="s2">"indpred"</span>, <span class="s2">"ix"</span>.<span class="s2">"indrelid"</span><span class="o">)</span> AS <span class="s2">"condition"</span>, <span class="s2">"types"</span>.<span class="s2">"typname"</span> AS <span class="s2">"type_name"</span> FROM <span class="s2">"pg_class"</span> <span class="s2">"t"</span> INNER JOIN <span class="s2">"pg_index"</span> <span class="s2">"ix"</span> ON <span class="s2">"ix"</span>.<span class="s2">"indrelid"</span> <span class="o">=</span> <span class="s2">"t"</span>.<span class="s2">"oid"</span> INNER JOIN <span class="s2">"pg_attribute"</span> <span class="s2">"a"</span> ON <span class="s2">"a"</span>.<span class="s2">"attrelid"</span> <span class="o">=</span> <span class="s2">"t"</span>.<span class="s2">"oid"</span> AND <span class="s2">"a"</span>.<span class="s2">"attnum"</span> <span class="o">=</span> ANY <span class="o">(</span><span class="s2">"ix"</span>.<span class="s2">"indkey"</span><span class="o">)</span> INNER JOIN <span class="s2">"pg_namespace"</span> <span class="s2">"ns"</span> ON <span class="s2">"ns"</span>.<span class="s2">"oid"</span> <span class="o">=</span> <span class="s2">"t"</span>.<span class="s2">"relnamespace"</span> INNER JOIN <span class="s2">"pg_class"</span> <span class="s2">"i"</span> ON <span class="s2">"i"</span>.<span class="s2">"oid"</span> <span class="o">=</span> <span class="s2">"ix"</span>.<span class="s2">"indexrelid"</span> INNER JOIN <span class="s2">"pg_type"</span> <span class="s2">"types"</span> ON <span class="s2">"types"</span>.<span class="s2">"oid"</span> <span class="o">=</span> <span class="s2">"a"</span>.<span class="s2">"atttypid"</span> LEFT JOIN <span class="s2">"pg_constraint"</span> <span class="s2">"cnst"</span> ON <span class="s2">"cnst"</span>.<span class="s2">"conname"</span> <span class="o">=</span> <span class="s2">"i"</span>.<span class="s2">"relname"</span> WHERE <span class="s2">"t"</span>.<span class="s2">"relkind"</span> IN <span class="o">(</span><span class="s1">'r'</span>, <span class="s1">'p'</span><span class="o">)</span> AND <span class="s2">"cnst"</span>.<span class="s2">"contype"</span> IS NULL AND <span class="o">((</span><span class="s2">"ns"</span>.<span class="s2">"nspname"</span> <span class="o">=</span> <span class="s1">'public'</span> AND <span class="s2">"t"</span>.<span class="s2">"relname"</span> <span class="o">=</span> <span class="s1">'orders'</span><span class="o">))</span>
query: SELECT <span class="s2">"con"</span>.<span class="s2">"conname"</span> AS <span class="s2">"constraint_name"</span>, <span class="s2">"con"</span>.<span class="s2">"nspname"</span> AS <span class="s2">"table_schema"</span>, <span class="s2">"con"</span>.<span class="s2">"relname"</span> AS <span class="s2">"table_name"</span>, <span class="s2">"att2"</span>.<span class="s2">"attname"</span> AS <span class="s2">"column_name"</span>, <span class="s2">"ns"</span>.<span class="s2">"nspname"</span> AS <span class="s2">"referenced_table_schema"</span>, <span class="s2">"cl"</span>.<span class="s2">"relname"</span> AS <span class="s2">"referenced_table_name"</span>, <span class="s2">"att"</span>.<span class="s2">"attname"</span> AS <span class="s2">"referenced_column_name"</span>, <span class="s2">"con"</span>.<span class="s2">"confdeltype"</span> AS <span class="s2">"on_delete"</span>, <span class="s2">"con"</span>.<span class="s2">"confupdtype"</span> AS <span class="s2">"on_update"</span>, <span class="s2">"con"</span>.<span class="s2">"condeferrable"</span> AS <span class="s2">"deferrable"</span>, <span class="s2">"con"</span>.<span class="s2">"condeferred"</span> AS <span class="s2">"deferred"</span> FROM <span class="o">(</span> SELECT UNNEST <span class="o">(</span><span class="s2">"con1"</span>.<span class="s2">"conkey"</span><span class="o">)</span> AS <span class="s2">"parent"</span>, UNNEST <span class="o">(</span><span class="s2">"con1"</span>.<span class="s2">"confkey"</span><span class="o">)</span> AS <span class="s2">"child"</span>, <span class="s2">"con1"</span>.<span class="s2">"confrelid"</span>, <span class="s2">"con1"</span>.<span class="s2">"conrelid"</span>, <span class="s2">"con1"</span>.<span class="s2">"conname"</span>, <span class="s2">"con1"</span>.<span class="s2">"contype"</span>, <span class="s2">"ns"</span>.<span class="s2">"nspname"</span>, <span class="s2">"cl"</span>.<span class="s2">"relname"</span>, <span class="s2">"con1"</span>.<span class="s2">"condeferrable"</span>, CASE WHEN <span class="s2">"con1"</span>.<span class="s2">"condeferred"</span> THEN <span class="s1">'INITIALLY DEFERRED'</span> ELSE <span class="s1">'INITIALLY IMMEDIATE'</span> END as condeferred, CASE <span class="s2">"con1"</span>.<span class="s2">"confdeltype"</span> WHEN <span class="s1">'a'</span> THEN <span class="s1">'NO ACTION'</span> WHEN <span class="s1">'r'</span> THEN <span class="s1">'RESTRICT'</span> WHEN <span class="s1">'c'</span> THEN <span class="s1">'CASCADE'</span> WHEN <span class="s1">'n'</span> THEN <span class="s1">'SET NULL'</span> WHEN <span class="s1">'d'</span> THEN <span class="s1">'SET DEFAULT'</span> END as <span class="s2">"confdeltype"</span>, CASE <span class="s2">"con1"</span>.<span class="s2">"confupdtype"</span> WHEN <span class="s1">'a'</span> THEN <span class="s1">'NO ACTION'</span> WHEN <span class="s1">'r'</span> THEN <span class="s1">'RESTRICT'</span> WHEN <span class="s1">'c'</span> THEN <span class="s1">'CASCADE'</span> WHEN <span class="s1">'n'</span> THEN <span class="s1">'SET NULL'</span> WHEN <span class="s1">'d'</span> THEN <span class="s1">'SET DEFAULT'</span> END as <span class="s2">"confupdtype"</span> FROM <span class="s2">"pg_class"</span> <span class="s2">"cl"</span> INNER JOIN <span class="s2">"pg_namespace"</span> <span class="s2">"ns"</span> ON <span class="s2">"cl"</span>.<span class="s2">"relnamespace"</span> <span class="o">=</span> <span class="s2">"ns"</span>.<span class="s2">"oid"</span> INNER JOIN <span class="s2">"pg_constraint"</span> <span class="s2">"con1"</span> ON <span class="s2">"con1"</span>.<span class="s2">"conrelid"</span> <span class="o">=</span> <span class="s2">"cl"</span>.<span class="s2">"oid"</span> WHERE <span class="s2">"con1"</span>.<span class="s2">"contype"</span> <span class="o">=</span> <span class="s1">'f'</span> AND <span class="o">((</span><span class="s2">"ns"</span>.<span class="s2">"nspname"</span> <span class="o">=</span> <span class="s1">'public'</span> AND <span class="s2">"cl"</span>.<span class="s2">"relname"</span> <span class="o">=</span> <span class="s1">'orders'</span><span class="o">))</span> <span class="o">)</span> <span class="s2">"con"</span> INNER JOIN <span class="s2">"pg_attribute"</span> <span class="s2">"att"</span> ON <span class="s2">"att"</span>.<span class="s2">"attrelid"</span> <span class="o">=</span> <span class="s2">"con"</span>.<span class="s2">"confrelid"</span> AND <span class="s2">"att"</span>.<span class="s2">"attnum"</span> <span class="o">=</span> <span class="s2">"con"</span>.<span class="s2">"child"</span> INNER JOIN <span class="s2">"pg_class"</span> <span class="s2">"cl"</span> ON <span class="s2">"cl"</span>.<span class="s2">"oid"</span> <span class="o">=</span> <span class="s2">"con"</span>.<span class="s2">"confrelid"</span> AND <span class="s2">"cl"</span>.<span class="s2">"relispartition"</span> <span class="o">=</span> <span class="s1">'f'</span>INNER JOIN <span class="s2">"pg_namespace"</span> <span class="s2">"ns"</span> ON <span class="s2">"cl"</span>.<span class="s2">"relnamespace"</span> <span class="o">=</span> <span class="s2">"ns"</span>.<span class="s2">"oid"</span> INNER JOIN <span class="s2">"pg_attribute"</span> <span class="s2">"att2"</span> ON <span class="s2">"att2"</span>.<span class="s2">"attrelid"</span> <span class="o">=</span> <span class="s2">"con"</span>.<span class="s2">"conrelid"</span> AND <span class="s2">"att2"</span>.<span class="s2">"attnum"</span> <span class="o">=</span> <span class="s2">"con"</span>.<span class="s2">"parent"</span>
query: ALTER TABLE <span class="s2">"orders"</span> ADD CONSTRAINT <span class="s2">"customer_id_fk"</span> FOREIGN KEY <span class="o">(</span><span class="s2">"customer_id"</span><span class="o">)</span> REFERENCES <span class="s2">"customers"</span><span class="o">(</span><span class="s2">"id"</span><span class="o">)</span>
query: INSERT INTO <span class="s2">"migrations"</span><span class="o">(</span><span class="s2">"timestamp"</span>, <span class="s2">"name"</span><span class="o">)</span> VALUES <span class="o">(</span><span class="nv">$1</span>, <span class="nv">$2</span><span class="o">)</span> <span class="nt">--</span> PARAMETERS: <span class="o">[</span>1648820154148,<span class="s2">"CreateOrders1648820154148"</span><span class="o">]</span>
Migration CreateOrders1648820154148 has been executed successfully.
query: CREATE TABLE <span class="s2">"products"</span> <span class="o">(</span><span class="s2">"id"</span> uuid NOT NULL DEFAULT uuid_generate_v4<span class="o">()</span>, <span class="s2">"name"</span> varchar NOT NULL, <span class="s2">"unit_price"</span> decimal<span class="o">(</span>12,2<span class="o">)</span> NOT NULL, <span class="s2">"is_discontinued"</span> boolean NOT NULL, <span class="s2">"description"</span> text NOT NULL, <span class="s2">"image"</span> varchar NOT NULL, <span class="s2">"created_at"</span> timestamptz NOT NULL DEFAULT now<span class="o">()</span>, <span class="s2">"updated_at"</span> timestamptz NOT NULL DEFAULT now<span class="o">()</span>, CONSTRAINT <span class="s2">"PK_0806c755e0aca124e67c0cf6d7d"</span> PRIMARY KEY <span class="o">(</span><span class="s2">"id"</span><span class="o">))</span>
query: INSERT INTO <span class="s2">"migrations"</span><span class="o">(</span><span class="s2">"timestamp"</span>, <span class="s2">"name"</span><span class="o">)</span> VALUES <span class="o">(</span><span class="nv">$1</span>, <span class="nv">$2</span><span class="o">)</span> <span class="nt">--</span> PARAMETERS: <span class="o">[</span>1648820162516,<span class="s2">"CreateProducts1648820162516"</span><span class="o">]</span>
Migration CreateProducts1648820162516 has been executed successfully.
query: CREATE TABLE <span class="s2">"order_items"</span> <span class="o">(</span><span class="s2">"customer_id"</span> uuid NOT NULL, <span class="s2">"product_id"</span> uuid NOT NULL, <span class="s2">"unit_price"</span> decimal<span class="o">(</span>12,2<span class="o">)</span> NOT NULL, <span class="s2">"quantity"</span> integer NOT NULL<span class="o">)</span>
query: SELECT <span class="k">*</span> FROM current_schema<span class="o">()</span>
query: SELECT <span class="k">*</span> FROM current_database<span class="o">()</span>
query: SELECT <span class="s2">"table_schema"</span>, <span class="s2">"table_name"</span> FROM <span class="s2">"information_schema"</span>.<span class="s2">"tables"</span> WHERE <span class="o">(</span><span class="s2">"table_schema"</span> <span class="o">=</span> <span class="s1">'public'</span> AND <span class="s2">"table_name"</span> <span class="o">=</span> <span class="s1">'order_items'</span><span class="o">)</span>
query: SELECT TRUE FROM information_schema.columns WHERE table_name <span class="o">=</span> <span class="s1">'pg_class'</span> and column_name <span class="o">=</span> <span class="s1">'relispartition'</span>
query: SELECT columns.<span class="k">*</span>, pg_catalog.col_description<span class="o">((</span><span class="s1">'"'</span> <span class="o">||</span> table_catalog <span class="o">||</span> <span class="s1">'"."'</span> <span class="o">||</span> table_schema <span class="o">||</span> <span class="s1">'"."'</span> <span class="o">||</span> table_name <span class="o">||</span> <span class="s1">'"'</span><span class="o">)</span>::regclass::oid, ordinal_position<span class="o">)</span> AS description, <span class="o">(</span><span class="s1">'"'</span> <span class="o">||</span> <span class="s2">"udt_schema"</span> <span class="o">||</span> <span class="s1">'"."'</span> <span class="o">||</span> <span class="s2">"udt_name"</span> <span class="o">||</span> <span class="s1">'"'</span><span class="o">)</span>::<span class="s2">"regtype"</span> AS <span class="s2">"regtype"</span>, pg_catalog.format_type<span class="o">(</span><span class="s2">"col_attr"</span>.<span class="s2">"atttypid"</span>, <span class="s2">"col_attr"</span>.<span class="s2">"atttypmod"</span><span class="o">)</span> AS <span class="s2">"format_type"</span> FROM <span class="s2">"information_schema"</span>.<span class="s2">"columns"</span> LEFT JOIN <span class="s2">"pg_catalog"</span>.<span class="s2">"pg_attribute"</span> AS <span class="s2">"col_attr"</span> ON <span class="s2">"col_attr"</span>.<span class="s2">"attname"</span> <span class="o">=</span> <span class="s2">"columns"</span>.<span class="s2">"column_name"</span> AND <span class="s2">"col_attr"</span>.<span class="s2">"attrelid"</span> <span class="o">=</span> <span class="o">(</span> SELECT <span class="s2">"cls"</span>.<span class="s2">"oid"</span> FROM <span class="s2">"pg_catalog"</span>.<span class="s2">"pg_class"</span> AS <span class="s2">"cls"</span> LEFT JOIN <span class="s2">"pg_catalog"</span>.<span class="s2">"pg_namespace"</span> AS <span class="s2">"ns"</span> ON <span class="s2">"ns"</span>.<span class="s2">"oid"</span> <span class="o">=</span> <span class="s2">"cls"</span>.<span class="s2">"relnamespace"</span> WHERE <span class="s2">"cls"</span>.<span class="s2">"relname"</span> <span class="o">=</span> <span class="s2">"columns"</span>.<span class="s2">"table_name"</span> AND <span class="s2">"ns"</span>.<span class="s2">"nspname"</span> <span class="o">=</span> <span class="s2">"columns"</span>.<span class="s2">"table_schema"</span> <span class="o">)</span> WHERE <span class="o">(</span><span class="s2">"table_schema"</span> <span class="o">=</span> <span class="s1">'public'</span> AND <span class="s2">"table_name"</span> <span class="o">=</span> <span class="s1">'order_items'</span><span class="o">)</span>
query: SELECT <span class="s2">"ns"</span>.<span class="s2">"nspname"</span> AS <span class="s2">"table_schema"</span>, <span class="s2">"t"</span>.<span class="s2">"relname"</span> AS <span class="s2">"table_name"</span>, <span class="s2">"cnst"</span>.<span class="s2">"conname"</span> AS <span class="s2">"constraint_name"</span>, pg_get_constraintdef<span class="o">(</span><span class="s2">"cnst"</span>.<span class="s2">"oid"</span><span class="o">)</span> AS <span class="s2">"expression"</span>, CASE <span class="s2">"cnst"</span>.<span class="s2">"contype"</span> WHEN <span class="s1">'p'</span> THEN <span class="s1">'PRIMARY'</span> WHEN <span class="s1">'u'</span> THEN <span class="s1">'UNIQUE'</span> WHEN <span class="s1">'c'</span> THEN <span class="s1">'CHECK'</span> WHEN <span class="s1">'x'</span> THEN <span class="s1">'EXCLUDE'</span> END AS <span class="s2">"constraint_type"</span>, <span class="s2">"a"</span>.<span class="s2">"attname"</span> AS <span class="s2">"column_name"</span> FROM <span class="s2">"pg_constraint"</span> <span class="s2">"cnst"</span> INNER JOIN <span class="s2">"pg_class"</span> <span class="s2">"t"</span> ON <span class="s2">"t"</span>.<span class="s2">"oid"</span> <span class="o">=</span> <span class="s2">"cnst"</span>.<span class="s2">"conrelid"</span> INNER JOIN <span class="s2">"pg_namespace"</span> <span class="s2">"ns"</span> ON <span class="s2">"ns"</span>.<span class="s2">"oid"</span> <span class="o">=</span> <span class="s2">"cnst"</span>.<span class="s2">"connamespace"</span> LEFT JOIN <span class="s2">"pg_attribute"</span> <span class="s2">"a"</span> ON <span class="s2">"a"</span>.<span class="s2">"attrelid"</span> <span class="o">=</span> <span class="s2">"cnst"</span>.<span class="s2">"conrelid"</span> AND <span class="s2">"a"</span>.<span class="s2">"attnum"</span> <span class="o">=</span> ANY <span class="o">(</span><span class="s2">"cnst"</span>.<span class="s2">"conkey"</span><span class="o">)</span> WHERE <span class="s2">"t"</span>.<span class="s2">"relkind"</span> IN <span class="o">(</span><span class="s1">'r'</span>, <span class="s1">'p'</span><span class="o">)</span> AND <span class="o">((</span><span class="s2">"ns"</span>.<span class="s2">"nspname"</span> <span class="o">=</span> <span class="s1">'public'</span> AND <span class="s2">"t"</span>.<span class="s2">"relname"</span> <span class="o">=</span> <span class="s1">'order_items'</span><span class="o">))</span>
query: SELECT <span class="s2">"ns"</span>.<span class="s2">"nspname"</span> AS <span class="s2">"table_schema"</span>, <span class="s2">"t"</span>.<span class="s2">"relname"</span> AS <span class="s2">"table_name"</span>, <span class="s2">"i"</span>.<span class="s2">"relname"</span> AS <span class="s2">"constraint_name"</span>, <span class="s2">"a"</span>.<span class="s2">"attname"</span> AS <span class="s2">"column_name"</span>, CASE <span class="s2">"ix"</span>.<span class="s2">"indisunique"</span> WHEN <span class="s1">'t'</span> THEN <span class="s1">'TRUE'</span> ELSE<span class="s1">'FALSE'</span> END AS <span class="s2">"is_unique"</span>, pg_get_expr<span class="o">(</span><span class="s2">"ix"</span>.<span class="s2">"indpred"</span>, <span class="s2">"ix"</span>.<span class="s2">"indrelid"</span><span class="o">)</span> AS <span class="s2">"condition"</span>, <span class="s2">"types"</span>.<span class="s2">"typname"</span> AS <span class="s2">"type_name"</span> FROM <span class="s2">"pg_class"</span> <span class="s2">"t"</span> INNER JOIN <span class="s2">"pg_index"</span> <span class="s2">"ix"</span> ON <span class="s2">"ix"</span>.<span class="s2">"indrelid"</span> <span class="o">=</span> <span class="s2">"t"</span>.<span class="s2">"oid"</span> INNER JOIN <span class="s2">"pg_attribute"</span> <span class="s2">"a"</span> ON <span class="s2">"a"</span>.<span class="s2">"attrelid"</span> <span class="o">=</span> <span class="s2">"t"</span>.<span class="s2">"oid"</span> AND <span class="s2">"a"</span>.<span class="s2">"attnum"</span> <span class="o">=</span> ANY <span class="o">(</span><span class="s2">"ix"</span>.<span class="s2">"indkey"</span><span class="o">)</span> INNER JOIN <span class="s2">"pg_namespace"</span> <span class="s2">"ns"</span> ON <span class="s2">"ns"</span>.<span class="s2">"oid"</span> <span class="o">=</span> <span class="s2">"t"</span>.<span class="s2">"relnamespace"</span> INNER JOIN <span class="s2">"pg_class"</span> <span class="s2">"i"</span> ON <span class="s2">"i"</span>.<span class="s2">"oid"</span> <span class="o">=</span> <span class="s2">"ix"</span>.<span class="s2">"indexrelid"</span> INNER JOIN <span class="s2">"pg_type"</span> <span class="s2">"types"</span> ON <span class="s2">"types"</span>.<span class="s2">"oid"</span> <span class="o">=</span> <span class="s2">"a"</span>.<span class="s2">"atttypid"</span> LEFT JOIN <span class="s2">"pg_constraint"</span> <span class="s2">"cnst"</span> ON <span class="s2">"cnst"</span>.<span class="s2">"conname"</span> <span class="o">=</span> <span class="s2">"i"</span>.<span class="s2">"relname"</span> WHERE <span class="s2">"t"</span>.<span class="s2">"relkind"</span> IN <span class="o">(</span><span class="s1">'r'</span>, <span class="s1">'p'</span><span class="o">)</span> AND <span class="s2">"cnst"</span>.<span class="s2">"contype"</span> IS NULL AND <span class="o">((</span><span class="s2">"ns"</span>.<span class="s2">"nspname"</span> <span class="o">=</span> <span class="s1">'public'</span> AND <span class="s2">"t"</span>.<span class="s2">"relname"</span> <span class="o">=</span> <span class="s1">'order_items'</span><span class="o">))</span>
query: SELECT <span class="s2">"con"</span>.<span class="s2">"conname"</span> AS <span class="s2">"constraint_name"</span>, <span class="s2">"con"</span>.<span class="s2">"nspname"</span> AS <span class="s2">"table_schema"</span>, <span class="s2">"con"</span>.<span class="s2">"relname"</span> AS <span class="s2">"table_name"</span>, <span class="s2">"att2"</span>.<span class="s2">"attname"</span> AS <span class="s2">"column_name"</span>, <span class="s2">"ns"</span>.<span class="s2">"nspname"</span> AS <span class="s2">"referenced_table_schema"</span>, <span class="s2">"cl"</span>.<span class="s2">"relname"</span> AS <span class="s2">"referenced_table_name"</span>, <span class="s2">"att"</span>.<span class="s2">"attname"</span> AS <span class="s2">"referenced_column_name"</span>, <span class="s2">"con"</span>.<span class="s2">"confdeltype"</span> AS <span class="s2">"on_delete"</span>, <span class="s2">"con"</span>.<span class="s2">"confupdtype"</span> AS <span class="s2">"on_update"</span>, <span class="s2">"con"</span>.<span class="s2">"condeferrable"</span> AS <span class="s2">"deferrable"</span>, <span class="s2">"con"</span>.<span class="s2">"condeferred"</span> AS <span class="s2">"deferred"</span> FROM <span class="o">(</span> SELECT UNNEST <span class="o">(</span><span class="s2">"con1"</span>.<span class="s2">"conkey"</span><span class="o">)</span> AS <span class="s2">"parent"</span>, UNNEST <span class="o">(</span><span class="s2">"con1"</span>.<span class="s2">"confkey"</span><span class="o">)</span> AS <span class="s2">"child"</span>, <span class="s2">"con1"</span>.<span class="s2">"confrelid"</span>, <span class="s2">"con1"</span>.<span class="s2">"conrelid"</span>, <span class="s2">"con1"</span>.<span class="s2">"conname"</span>, <span class="s2">"con1"</span>.<span class="s2">"contype"</span>, <span class="s2">"ns"</span>.<span class="s2">"nspname"</span>, <span class="s2">"cl"</span>.<span class="s2">"relname"</span>, <span class="s2">"con1"</span>.<span class="s2">"condeferrable"</span>, CASE WHEN <span class="s2">"con1"</span>.<span class="s2">"condeferred"</span> THEN <span class="s1">'INITIALLY DEFERRED'</span> ELSE <span class="s1">'INITIALLY IMMEDIATE'</span> END as condeferred, CASE <span class="s2">"con1"</span>.<span class="s2">"confdeltype"</span> WHEN <span class="s1">'a'</span> THEN <span class="s1">'NO ACTION'</span> WHEN <span class="s1">'r'</span> THEN <span class="s1">'RESTRICT'</span> WHEN <span class="s1">'c'</span> THEN <span class="s1">'CASCADE'</span> WHEN <span class="s1">'n'</span> THEN <span class="s1">'SET NULL'</span> WHEN <span class="s1">'d'</span> THEN <span class="s1">'SET DEFAULT'</span> END as <span class="s2">"confdeltype"</span>, CASE <span class="s2">"con1"</span>.<span class="s2">"confupdtype"</span> WHEN <span class="s1">'a'</span> THEN <span class="s1">'NO ACTION'</span> WHEN <span class="s1">'r'</span> THEN <span class="s1">'RESTRICT'</span> WHEN <span class="s1">'c'</span> THEN <span class="s1">'CASCADE'</span> WHEN <span class="s1">'n'</span> THEN <span class="s1">'SET NULL'</span> WHEN <span class="s1">'d'</span> THEN <span class="s1">'SET DEFAULT'</span> END as <span class="s2">"confupdtype"</span> FROM <span class="s2">"pg_class"</span> <span class="s2">"cl"</span> INNER JOIN <span class="s2">"pg_namespace"</span> <span class="s2">"ns"</span> ON <span class="s2">"cl"</span>.<span class="s2">"relnamespace"</span> <span class="o">=</span> <span class="s2">"ns"</span>.<span class="s2">"oid"</span> INNER JOIN <span class="s2">"pg_constraint"</span> <span class="s2">"con1"</span> ON <span class="s2">"con1"</span>.<span class="s2">"conrelid"</span> <span class="o">=</span> <span class="s2">"cl"</span>.<span class="s2">"oid"</span> WHERE <span class="s2">"con1"</span>.<span class="s2">"contype"</span> <span class="o">=</span> <span class="s1">'f'</span> AND <span class="o">((</span><span class="s2">"ns"</span>.<span class="s2">"nspname"</span> <span class="o">=</span> <span class="s1">'public'</span> AND <span class="s2">"cl"</span>.<span class="s2">"relname"</span> <span class="o">=</span> <span class="s1">'order_items'</span><span class="o">))</span> <span class="o">)</span> <span class="s2">"con"</span> INNER JOIN <span class="s2">"pg_attribute"</span> <span class="s2">"att"</span> ON <span class="s2">"att"</span>.<span class="s2">"attrelid"</span> <span class="o">=</span> <span class="s2">"con"</span>.<span class="s2">"confrelid"</span> AND <span class="s2">"att"</span>.<span class="s2">"attnum"</span> <span class="o">=</span> <span class="s2">"con"</span>.<span class="s2">"child"</span> INNER JOIN <span class="s2">"pg_class"</span> <span class="s2">"cl"</span> ON <span class="s2">"cl"</span>.<span class="s2">"oid"</span> <span class="o">=</span> <span class="s2">"con"</span>.<span class="s2">"confrelid"</span> AND <span class="s2">"cl"</span>.<span class="s2">"relispartition"</span> <span class="o">=</span> <span class="s1">'f'</span>INNER JOIN <span class="s2">"pg_namespace"</span> <span class="s2">"ns"</span> ON <span class="s2">"cl"</span>.<span class="s2">"relnamespace"</span> <span class="o">=</span> <span class="s2">"ns"</span>.<span class="s2">"oid"</span> INNER JOIN <span class="s2">"pg_attribute"</span> <span class="s2">"att2"</span> ON <span class="s2">"att2"</span>.<span class="s2">"attrelid"</span> <span class="o">=</span> <span class="s2">"con"</span>.<span class="s2">"conrelid"</span> AND <span class="s2">"att2"</span>.<span class="s2">"attnum"</span> <span class="o">=</span> <span class="s2">"con"</span>.<span class="s2">"parent"</span>
query: ALTER TABLE <span class="s2">"order_items"</span> ADD CONSTRAINT <span class="s2">"order_id_fk"</span> FOREIGN KEY <span class="o">(</span><span class="s2">"order_id"</span><span class="o">)</span> REFERENCES <span class="s2">"orders"</span><span class="o">(</span><span class="s2">"id"</span><span class="o">)</span>
query: ALTER TABLE <span class="s2">"order_items"</span> ADD CONSTRAINT <span class="s2">"product_id_fk"</span> FOREIGN KEY <span class="o">(</span><span class="s2">"product_id"</span><span class="o">)</span> REFERENCES <span class="s2">"products"</span><span class="o">(</span><span class="s2">"id"</span><span class="o">)</span>
query: INSERT INTO <span class="s2">"migrations"</span><span class="o">(</span><span class="s2">"timestamp"</span>, <span class="s2">"name"</span><span class="o">)</span> VALUES <span class="o">(</span><span class="nv">$1</span>, <span class="nv">$2</span><span class="o">)</span> <span class="nt">--</span> PARAMETERS: <span class="o">[</span>1649079742594,<span class="s2">"CreateOrderItems1649079742594"</span><span class="o">]</span>
Migration CreateOrderItems1649079742594 has been executed successfully.
query: COMMIT
</code></pre></div></div>
<p>According to the above log, all our pending migrations have executed successfully.</p>
<h3 id="sources">SOURCES</h3>
<ul>
<li>TypeORM Migrations Documentation: <a href="https://typeorm.io/migrations">https://typeorm.io/migrations</a></li>
<li>Nest.js Database Documentation: <a href="https://docs.nestjs.com/techniques/database">https://docs.nestjs.com/techniques/database</a></li>
</ul>Isaiah MucheneWe shall be continuing from where we left off in the previous tutorial by building the database backend (using Postgresql) for the storefront backend.Bootstrapping a Nest.js Project on Ubuntu2022-04-07T12:53:00+03:002022-04-07T12:53:00+03:00/2022/04/bootstrapping-nestjs-ubuntu<div class="card seriesNote">
<p class="text-center">
This article is <strong>Part 1</strong> in a <strong>10 Part</strong> Series.
</p>
<ul class="list-group list-group-flush">
<li class="list-group-item"> Part 1 -
This Article
</li>
<li class="list-group-item"> Part 2 -
<a href="/2022/04/create-nestjs-migrations">Create Nest.js Migrations</a>
</li>
<li class="list-group-item"> Part 3 -
<a href="/2022/04/create-nestjs-crud-resources">Create Nest.js CRUD Resources</a>
</li>
<li class="list-group-item"> Part 4 -
<a href="/2022/06/create-nestjs-seeders">Create Nest.js Seeders</a>
</li>
<li class="list-group-item"> Part 5 -
<a href="/2022/07/customer-module">Create a Customer Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 6 -
<a href="/2022/07/auth-module">Nest.js Authentication</a>
</li>
<li class="list-group-item"> Part 7 -
<a href="/2022/07/products-module">Create a Products Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 8 -
<a href="/2022/07/stripe-module">Create a Stripe Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 9 -
<a href="/2022/08/orders-module">Create an Orders Module in Nest.js</a>
</li>
<li class="list-group-item"> Part 10 -
<a href="/2022/08/end-to-end-tests">End to End Tests for our Storefront Backend</a>
</li>
</ul>
</div>
<p><br /></p>
<p><a href="https://docs.nestjs.com">Nest.js</a> is a web framework used to build web applications that fully supports both Typescript and modern Javascript. It differs from other web frameworks such as Ruby on Rails or Laravel in that it doesn’t force a convention on you such as <a href="https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller">MVC</a> (Model-View-Controller), which has its advantages and disadvantages. I have used Nest.js quite extensively in my previous employment as well as in side projects, and so far I have come to love using it with Typescript. One of the benefits is support for type checking and strong typing with Typescript - errors in your code are very quickly pointed out to you. Another is very fast execution thanks to the Node.js compilation, even for large projects like in my previous employment at <a href="https://orteo.co/">Orteo</a>. If you choose to go with Typescript, your code will be compiled to Javascript, and the framework uses either <a href="https://expressjs.com/">Express</a> or <a href="https://github.com/fastify/fastify">Fastify</a> as the application server under the hood.</p>
<p>While developing a node.js or Nest.js application locally, I would advise using a node.js version manager such as <a href="https://github.com/nvm-sh/nvm#installing-and-updating">nvm</a>. This has the benefit of allowing you to easily upgrade to new versions of node.js in future, while still maintaining support for your older projects. In production environments, I would advise adding the NodeSource PPA, then installing Node.js from their repository. For this tutorial, since we will be developing an API only e-commerce backend called <strong>storefont-backend</strong> , I will use the nvm method to install node.js. We will be installing the latest LTS version (16) at the time of writing. We will also be using Postgresql as our relational database, so we shall install it as well. We shall install Postgresql 14.</p>
<p>In case of any issues, you can refer to my Github repository <a href="https://github.com/imuchene/storefront-backend">here</a>.</p>
<h3 id="install-nvm-nodejs-and-npm">Install NVM, Node.js and NPM</h3>
<p>Fetch nvm from Github.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-o-</span> https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
</code></pre></div></div>
<p>Reload your bash configuration.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">source</span> ~/.bashrc
</code></pre></div></div>
<p>Use nvm to check for the latest LTS versions of Node.js</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nvm list-remote <span class="nt">--lts</span>
</code></pre></div></div>
<p>Output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code> v12.22.11 <span class="o">(</span>LTS: Erbium<span class="o">)</span>
v12.22.12 <span class="o">(</span>Latest LTS: Erbium<span class="o">)</span>
v14.15.0 <span class="o">(</span>LTS: Fermium<span class="o">)</span>
v14.15.1 <span class="o">(</span>LTS: Fermium<span class="o">)</span>
v14.15.2 <span class="o">(</span>LTS: Fermium<span class="o">)</span>
v14.15.3 <span class="o">(</span>LTS: Fermium<span class="o">)</span>
v14.15.4 <span class="o">(</span>LTS: Fermium<span class="o">)</span>
v14.15.5 <span class="o">(</span>LTS: Fermium<span class="o">)</span>
v14.16.0 <span class="o">(</span>LTS: Fermium<span class="o">)</span>
v14.16.1 <span class="o">(</span>LTS: Fermium<span class="o">)</span>
v14.17.0 <span class="o">(</span>LTS: Fermium<span class="o">)</span>
v14.17.1 <span class="o">(</span>LTS: Fermium<span class="o">)</span>
v14.17.2 <span class="o">(</span>LTS: Fermium<span class="o">)</span>
v14.17.3 <span class="o">(</span>LTS: Fermium<span class="o">)</span>
v14.17.4 <span class="o">(</span>LTS: Fermium<span class="o">)</span>
v14.17.5 <span class="o">(</span>LTS: Fermium<span class="o">)</span>
v14.17.6 <span class="o">(</span>LTS: Fermium<span class="o">)</span>
v14.18.0 <span class="o">(</span>LTS: Fermium<span class="o">)</span>
v14.18.1 <span class="o">(</span>LTS: Fermium<span class="o">)</span>
v14.18.2 <span class="o">(</span>LTS: Fermium<span class="o">)</span>
v14.18.3 <span class="o">(</span>LTS: Fermium<span class="o">)</span>
v14.19.0 <span class="o">(</span>LTS: Fermium<span class="o">)</span>
v14.19.1 <span class="o">(</span>Latest LTS: Fermium<span class="o">)</span>
v16.13.0 <span class="o">(</span>LTS: Gallium<span class="o">)</span>
v16.13.1 <span class="o">(</span>LTS: Gallium<span class="o">)</span>
v16.13.2 <span class="o">(</span>LTS: Gallium<span class="o">)</span>
v16.14.0 <span class="o">(</span>LTS: Gallium<span class="o">)</span>
v16.14.1 <span class="o">(</span>LTS: Gallium<span class="o">)</span>
v16.14.2 <span class="o">(</span>Latest LTS: Gallium<span class="o">)</span>
</code></pre></div></div>
<p>Install the latest Node.js LTS (version 16).</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nvm <span class="nb">install</span> <span class="nt">--lts</span><span class="o">=</span>gallium
</code></pre></div></div>
<p>Set Node version 16 to be the default version used by the terminal.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nvm use v16.14.2
</code></pre></div></div>
<p>Confirm that Node is correctly installed.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>node <span class="nt">--version</span>
</code></pre></div></div>
<p>Output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>v16.14.2
</code></pre></div></div>
<p>Confirm that NPM (Node package Manager) exists and is correctly installed.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm <span class="nt">--version</span>
</code></pre></div></div>
<p>Output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>8.1.2
</code></pre></div></div>
<h3 id="install-postgresql">Install Postgresql</h3>
<p>Since we want to use the latest version of Postgresql, we will have to add an external repository that’s maintained by the Postgresql team.</p>
<p>Add the external repository to your sources list:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>sh <span class="nt">-c</span> <span class="s1">'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'</span>
</code></pre></div></div>
<p>Add the GPG public key from the Postgresql team to verify the repository.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget <span class="nt">--quiet</span> <span class="nt">-O</span> - https://www.postgresql.org/media/keys/ACCC4CF8.asc | <span class="nb">sudo </span>apt-key add -
</code></pre></div></div>
<p>Update your local repository.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get update
</code></pre></div></div>
<p>Install Postgresql version 14.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get <span class="nt">-y</span> <span class="nb">install </span>postgresql-14
</code></pre></div></div>
<p>Confirm that Postgresql is installed correctly.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo</span> <span class="nt">-u</span> postgres psql <span class="nt">-c</span> <span class="s2">"select version();"</span>
</code></pre></div></div>
<p>Output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code> version
<span class="nt">-----------------------------------------------------------------------------------------------------------------------------</span>
PostgreSQL 14.2 <span class="o">(</span>Ubuntu 14.2-1.pgdg21.04+1<span class="o">)</span> on x86_64-pc-linux-gnu, compiled by gcc <span class="o">(</span>Ubuntu 10.3.0-1ubuntu1<span class="o">)</span> 10.3.0, 64-bit
<span class="o">(</span>1 row<span class="o">)</span>
</code></pre></div></div>
<p>Press <strong>q</strong> to exit from the postgresql terminal.</p>
<h3 id="install-git">Install Git</h3>
<p>Since the pre-installed Git that comes with Ubuntu/PopOS isn’t the latest one, we shall install the latest version from the PPA maintained by the Git core team.</p>
<p>Add the Git core team’s PPA to the sources list.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>add-apt-repository ppa:git-core/ppa
</code></pre></div></div>
<p>Update your local APT repository.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt update
</code></pre></div></div>
<p>Install the latest version of git.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt <span class="nb">install </span>git
</code></pre></div></div>
<p>Confirm that git is correctly installed.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git <span class="nt">--version</span>
</code></pre></div></div>
<p>Output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git version 2.32.0
</code></pre></div></div>
<h3 id="install-nestjs-cli-globally">Install Nest.js CLI globally</h3>
<p>The CLI (Command Line Interface) is a very handy utility that we will be using later on to create the main project, as well as to generate various resources required in the project e.g. modules, controllers and services. We are installing it globally so as to have the ability to call it anywhere in our file system without requiring an absolute path to the executable.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i <span class="nt">-g</span> @nestjs/cli
</code></pre></div></div>
<p>Confirm that the CLI is installed correctly.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nest <span class="nt">--version</span>
</code></pre></div></div>
<p>Output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>8.2.0
</code></pre></div></div>
<h3 id="optional-install-vs-code">(Optional) Install VS-Code</h3>
<p>Since my previous employment, VS-Code has become my preferred code editor. It has the benefits of being cross platform, a large community with vast language support as well as being quite user friendly. It’s not a requirement you use it for this tutorial, but you’re welcome to try it out (if you haven’t already).</p>
<p>Add the Microsoft public key to your local key cache.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget <span class="nt">-q</span> https://packages.microsoft.com/keys/microsoft.asc <span class="nt">-O-</span> | <span class="nb">sudo </span>apt-key add -
</code></pre></div></div>
<p>Add the VS code repository from Microsoft.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>add-apt-repository <span class="s2">"deb [arch=amd64] https://packages.microsoft.com/repos/vscode stable main"</span>
</code></pre></div></div>
<p>Install VS code.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt <span class="nb">install </span>code
</code></pre></div></div>
<p>Confirm that VS code is installed correctly.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>code <span class="nt">--version</span>
</code></pre></div></div>
<p>Output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1.64.2
f80445acd5a3dadef24aa209168452a3d97cc326
x64
</code></pre></div></div>
<h3 id="optional-generate-ssh-keys-locally">(Optional) Generate SSH Keys locally</h3>
<p>In my local development environment, I prefer using SSH Keys over HTTP authentication, since with SSH I will not be always prompted for a username/password when interacting with a remote repository. This step is also optional. To setup SSH keys locally, you can follow the tutorial from Github <a href="https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent">here</a>.</p>
<h3 id="optional-create-a-github-account-and-add-ssh-keys-to-github">(Optional) Create a Github account and add SSH keys to Github</h3>
<p>You can create a Github account by visiting their site <a href="https://github.com">here</a> and clicking sign up. Then, in order to add your newly created SSH keys to Github, follow their tutorial <a href="https://docs.github.com/en/authentication/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account">here</a>.</p>
<h3 id="optional-add-github-account-to-vs-code">(Optional) Add Github account to VS-Code</h3>
<p>Github has a VS-Code extension that makes creating Github repos, as well as pushing and pulling code from those repos a breeze. To install the extension, as well as to find out additional capabilities of said extension, follow the VS Code user guide <a href="https://code.visualstudio.com/docs/editor/github">here</a>.</p>
<h3 id="create-a-projects-directory-if-you-dont-have-one-already">Create a projects directory (if you don’t have one already)</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> <span class="nt">-p</span> ~/Projects/nest_projects
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> ~/Projects/nest_projects
</code></pre></div></div>
<h3 id="create-a-new-nestjs-project-called-storefront-backend">Create a new Nest.js project called storefront-backend</h3>
<p><strong>Note:</strong> I will be using npm as my preferred package manager.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nest new storefront-backend
</code></pre></div></div>
<p>Output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>⚡ We will scaffold your app <span class="k">in </span>a few seconds..
CREATE storefront-backend/.eslintrc.js <span class="o">(</span>631 bytes<span class="o">)</span>
CREATE storefront-backend/.prettierrc <span class="o">(</span>51 bytes<span class="o">)</span>
CREATE storefront-backend/README.md <span class="o">(</span>3339 bytes<span class="o">)</span>
CREATE storefront-backend/nest-cli.json <span class="o">(</span>64 bytes<span class="o">)</span>
CREATE storefront-backend/package.json <span class="o">(</span>2007 bytes<span class="o">)</span>
CREATE storefront-backend/tsconfig.build.json <span class="o">(</span>97 bytes<span class="o">)</span>
CREATE storefront-backend/tsconfig.json <span class="o">(</span>546 bytes<span class="o">)</span>
CREATE storefront-backend/src/app.controller.spec.ts <span class="o">(</span>617 bytes<span class="o">)</span>
CREATE storefront-backend/src/app.controller.ts <span class="o">(</span>274 bytes<span class="o">)</span>
CREATE storefront-backend/src/app.module.ts <span class="o">(</span>249 bytes<span class="o">)</span>
CREATE storefront-backend/src/app.service.ts <span class="o">(</span>142 bytes<span class="o">)</span>
CREATE storefront-backend/src/main.ts <span class="o">(</span>208 bytes<span class="o">)</span>
CREATE storefront-backend/test/app.e2e-spec.ts <span class="o">(</span>630 bytes<span class="o">)</span>
CREATE storefront-backend/test/jest-e2e.json <span class="o">(</span>183 bytes<span class="o">)</span>
? Which package manager would you ❤️ to use? npm
✔ Installation <span class="k">in </span>progress... ☕
🚀 Successfully created project storefront-backend
👉 Get started with the following commands:
<span class="nv">$ </span><span class="nb">cd </span>storefront-backend
<span class="nv">$ </span>npm run start
Thanks <span class="k">for </span>installing Nest 🙏
Please consider donating to our open collective
to <span class="nb">help </span>us maintain this package.
🍷 Donate: https://opencollective.com/nest
</code></pre></div></div>
<h3 id="on-a-different-terminal-tab-navigate-to-the-newly-generated-nestjs-project">On a different terminal tab, navigate to the newly generated Nest.js project</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> ~/Projects/nest_projects/storefront-backend/
</code></pre></div></div>
<h3 id="run-the-project-and-autoload-any-changes-made-to-the-project">Run the project, and autoload any changes made to the project</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm run start:dev
</code></pre></div></div>
<h3 id="optional-update-nestjs-to-the-latest-version-824-at-the-time-of-writing">(Optional) Update Nest.js to the latest version (8.2.4 at the time of writing)</h3>
<p><strong>Note:</strong> Don’t try this on an old Nest.js project as it will install breaking changes</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nest u <span class="nt">-f</span> <span class="nt">-t</span> latest
</code></pre></div></div>
<p>The above command will force install the latest version of Nest.js and ignore any breaking changes between dependencies.</p>
<h3 id="confirm-that-the-latest-version-of-nestjs-is-installed">Confirm that the latest version of Nest.js is installed</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nest info
</code></pre></div></div>
<p>Output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code> _ _ _ ___ _____ _____ _ _____
| <span class="se">\ </span>| | | | |_|/ ___|/__ <span class="se">\|</span> | |__|
| <span class="se">\|</span> | ______ | |_| |<span class="se">\ </span><span class="sb">`</span><span class="nt">--</span><span class="nb">.</span> | / <span class="se">\/</span>| | | |
| <span class="nb">.</span> <span class="sb">`</span> | /_ <span class="se">\/</span> __||__| | | <span class="sb">`</span><span class="nt">--</span><span class="nb">.</span> <span class="se">\|</span> | | | | |
| |<span class="se">\ </span> <span class="o">||</span> __/<span class="se">\_</span>_ <span class="se">\|</span> |_ /<span class="se">\_</span>_/ //<span class="se">\_</span>_/ /| <span class="se">\_</span>_/<span class="se">\|</span> |_____| |_
<span class="se">\_</span>| <span class="se">\_</span>/ <span class="se">\_</span><span class="k">**</span><span class="o">||</span><span class="k">**</span>_/ <span class="se">\_</span>_|<span class="se">\_</span>___/ <span class="se">\_</span>___/ <span class="se">\_</span>___/<span class="se">\_</span>____/<span class="se">\_</span>__/
<span class="o">[</span>System Information]
OS Version : Linux 5.15
NodeJS Version : v16.13.1
NPM Version : 8.1.2
<span class="o">[</span>Nest CLI]
Nest CLI Version : 8.2.4
<span class="o">[</span>Nest Platform Information]
platform-express version : 8.4.3
schematics version : 8.0.8
testing version : 8.4.3
common version : 8.4.3
core version : 8.4.3
cli version : 8.2.4
</code></pre></div></div>
<h3 id="confirm-that-the-server-is-running-and-printing-logs-on-the-console">Confirm that the server is running and printing logs on the console</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm run start:dev
</code></pre></div></div>
<p>Output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>4:22:52 PM] Starting compilation <span class="k">in </span>watch mode...
<span class="o">[</span>4:22:53 PM] Found 0 errors. Watching <span class="k">for </span>file changes.
<span class="o">[</span>Nest] 153346 - 03/29/2022, 4:22:53 PM LOG <span class="o">[</span>NestFactory] Starting Nest application...
<span class="o">[</span>Nest] 153346 - 03/29/2022, 4:22:53 PM LOG <span class="o">[</span>InstanceLoader] AppModule dependencies initialized +17ms
<span class="o">[</span>Nest] 153346 - 03/29/2022, 4:22:53 PM LOG <span class="o">[</span>RoutesResolver] AppController <span class="o">{</span>/<span class="o">}</span>: +3ms
<span class="o">[</span>Nest] 153346 - 03/29/2022, 4:22:53 PM LOG <span class="o">[</span>RouterExplorer] Mapped <span class="o">{</span>/, GET<span class="o">}</span> route +2ms
<span class="o">[</span>Nest] 153346 - 03/29/2022, 4:22:53 PM LOG <span class="o">[</span>NestApplication] Nest application successfully started +1ms
</code></pre></div></div>
<p>If you navigate to <a href="http://localhost:3000">http://localhost:3000</a> on your web browser you should see something like this:</p>
<p><img src="/assets/storefront/01-Nest.js-running-on-port-3000.png" alt="Github Vs-Code" /></p>
<p>A Hello World message is displayed on the browser.</p>
<h3 id="create-a-development-branch-using-git-on-the-project">Create a development branch using git on the project</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git co <span class="nt">-b</span> development
</code></pre></div></div>
<p>Output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Switched to a new branch <span class="s1">'development'</span>
</code></pre></div></div>
<h3 id="add-the-newly-generated-files-and-commit-them">Add the newly generated files and commit them</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git add <span class="nb">.</span>
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git ci <span class="nt">-m</span> <span class="s2">"Initial commit"</span>
</code></pre></div></div>
<p>Output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>development <span class="o">(</span>root-commit<span class="o">)</span> 215a757] Initial commit
16 files changed, 15809 insertions<span class="o">(</span>+<span class="o">)</span>
create mode 100644 .eslintrc.js
create mode 100644 .gitignore
create mode 100644 .prettierrc
create mode 100644 README.md
create mode 100644 nest-cli.json
create mode 100644 package-lock.json
create mode 100644 package.json
create mode 100644 src/app.controller.spec.ts
create mode 100644 src/app.controller.ts
create mode 100644 src/app.module.ts
create mode 100644 src/app.service.ts
create mode 100644 src/main.ts
create mode 100644 <span class="nb">test</span>/app.e2e-spec.ts
create mode 100644 <span class="nb">test</span>/jest-e2e.json
create mode 100644 tsconfig.build.json
create mode 100644 tsconfig.json
</code></pre></div></div>
<h3 id="optional-use-vs-code-to-publish-project-to-publicprivate-github-repository">(Optional) Use VS-Code to publish project to public/private Github Repository</h3>
<p>VS-Code will use the added Github account to push code to your Github repo. You should see something similar to the graphic below:</p>
<p><img src="/assets/storefront/02-Push-Code-to-Github.png" alt="Github Vs-Code" /></p>
<p>It’s your choice as to whether you want your repo to be public or private. Regardless, the repo will be automatically created and your initial commit will appear on Github.</p>
<h3 id="sources">SOURCES</h3>
<ul>
<li>
<p><a href="https://www.digitalocean.com/community/tutorials/how-to-install-node-js-on-ubuntu-20-04">https://www.digitalocean.com/community/tutorials/how-to-install-node-js-on-ubuntu-20-04</a></p>
</li>
<li>
<p><a href="https://www.postgresql.org/download/linux/ubuntu/">https://www.postgresql.org/download/linux/ubuntu/</a></p>
</li>
<li>
<p><a href="https://itsfoss.com/install-git-ubuntu/">https://itsfoss.com/install-git-ubuntu/</a></p>
</li>
<li>
<p><a href="https://www.makeuseof.com/how-to-install-visual-studio-code-ubuntu/">https://www.makeuseof.com/how-to-install-visual-studio-code-ubuntu/</a></p>
</li>
<li>
<p><a href="https://code.visualstudio.com/docs/editor/github">https://code.visualstudio.com/docs/editor/github</a></p>
</li>
</ul>Isaiah MucheneNest.js is a web framework used to build web applications that fully supports both Typescript and modern Javascript. It differs from other web frameworks such as Ruby on Rails or Laravel in that it doesn’t force a convention on you such as MVC (Model-View-Controller),