Jekyll2024-01-16T10:29:21+00:00https://agiliq.com/AgiliqAgiliq builds amazing apps using modern tools.Invoke an AWS.Lambda function from another lambda function2022-05-29T00:00:00+00:002022-05-29T00:00:00+00:00https://agiliq.com/blog/2022/05/invoke-aws-lambda-from-another-lambda-function<p>When we have multiple lambda functions which are dependent on one another then we may need the execution output of another lambda function in the current lambda function inorder to process it.</p>
<p>In such cases, we need to call/invoke the dependent lambda function from the current lambda function.</p>
<p>Let’s say we have two lambda functions</p>
<ol>
<li>process PDF contents - <code class="highlighter-rouge">process_pdf_invoice</code></li>
<li>update PDF contents in the database - <code class="highlighter-rouge">save_invoice_info</code></li>
</ol>
<p>We will be using <strong>nodejs</strong> to implement this scenario.</p>
<h2 id="function---process_pdf_invoice">Function - process_pdf_invoice</h2>
<ul>
<li>Let’s write the code for <code class="highlighter-rouge">process_pdf_invoice</code> lambda function.</li>
</ul>
<blockquote>
<p>process_pdf_invoice.js</p>
</blockquote>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">exports</span><span class="p">.</span><span class="nx">handler</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">event</span><span class="p">,</span> <span class="nx">context</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Let's return some dummy data</span>
<span class="kd">const</span> <span class="nx">invoice</span> <span class="o">=</span> <span class="p">{</span>
<span class="s2">"DueDate"</span><span class="p">:</span> <span class="s2">"2013-02-15"</span><span class="p">,</span>
<span class="s2">"Balance"</span><span class="p">:</span> <span class="mf">1990.19</span><span class="p">,</span>
<span class="s2">"DocNumber"</span><span class="p">:</span> <span class="s2">"SAMP001"</span><span class="p">,</span>
<span class="s2">"Status"</span><span class="p">:</span> <span class="s2">"Payable"</span><span class="p">,</span>
<span class="s2">"Line"</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="s2">"Description"</span><span class="p">:</span> <span class="s2">"Sample Expense"</span><span class="p">,</span>
<span class="s2">"Amount"</span><span class="p">:</span> <span class="mi">500</span><span class="p">,</span>
<span class="s2">"DetailType"</span><span class="p">:</span> <span class="s2">"ExpenseDetail"</span><span class="p">,</span>
<span class="s2">"Customer"</span><span class="p">:</span> <span class="s2">"ABC123 (Sample Customer)"</span><span class="p">,</span>
<span class="s2">"Ref"</span><span class="p">:</span> <span class="s2">"DEF234 (Sample Construction)"</span><span class="p">,</span>
<span class="s2">"Account"</span><span class="p">:</span> <span class="s2">"EFG345 (Fuel)"</span><span class="p">,</span>
<span class="s2">"LineStatus"</span><span class="p">:</span> <span class="s2">"Billable"</span>
<span class="p">}</span>
<span class="p">],</span>
<span class="s2">"TotalAmt"</span><span class="p">:</span> <span class="mf">1990.19</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">invoice</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="function---save_invoice_info">Function - save_invoice_info</h2>
<ul>
<li>Let’s write the code for lambda function <code class="highlighter-rouge">save_invoice_info</code></li>
<li>To invoke the lambda function we need AWS SDK (i.e <a href="https://www.npmjs.com/package/aws-sdk">aws-sdk</a>).</li>
<li>By default it will be available in AWS lambda if not we need to install it as a layer.</li>
</ul>
<blockquote>
<p>save_invoice_info.js</p>
</blockquote>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">AWS</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">'aws-sdk'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">lambda</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">AWS</span><span class="p">.</span><span class="nx">Lambda</span><span class="p">();</span>
<span class="k">async</span> <span class="kd">function</span> <span class="nx">getInvoiceInfo</span><span class="p">(){</span>
<span class="c1">// params to send to lambda</span>
<span class="kd">const</span> <span class="nx">params</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">FunctionName</span><span class="p">:</span> <span class="s1">'process_pdf_invoice'</span><span class="p">,</span>
<span class="na">InvocationType</span><span class="p">:</span> <span class="s1">'RequestResponse'</span><span class="p">,</span>
<span class="na">LogType</span><span class="p">:</span> <span class="s1">'None'</span><span class="p">,</span>
<span class="na">Payload</span><span class="p">:</span> <span class="s1">'{}'</span><span class="p">,</span>
<span class="p">};</span>
<span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">lambda</span><span class="p">.</span><span class="nx">invoke</span><span class="p">(</span><span class="nx">params</span><span class="p">).</span><span class="nx">promise</span><span class="p">();</span>
<span class="k">if</span><span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nx">StatusCode</span> <span class="o">!==</span> <span class="mi">200</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="s1">'Failed to get response from lambda function'</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nx">Payload</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">exports</span><span class="p">.</span><span class="nx">handler</span> <span class="o">=</span> <span class="k">async</span> <span class="kd">function</span><span class="p">(</span><span class="nx">event</span><span class="p">,</span> <span class="nx">context</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// invoke and get info from `process_pdf_invoice`</span>
<span class="kd">const</span> <span class="nx">invoice</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">getInvoiceInfo</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="s1">'invoice'</span><span class="p">,</span> <span class="nx">invoice</span><span class="p">);</span>
<span class="c1">// now write the code to save data into database</span>
<span class="k">return</span> <span class="p">{</span><span class="s1">'status'</span><span class="p">:</span> <span class="s1">'saved'</span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<ul>
<li>The code <code class="highlighter-rouge">lambda.invoke(params).promise()</code> will invoke lambda function and returns the response.</li>
<li>If the invokation is success then it will return <code class="highlighter-rouge">200</code> otherwise it will return <code class="highlighter-rouge">5XX</code> code.</li>
<li><code class="highlighter-rouge">response.Payload</code> will give the response returned by the lambda function.</li>
</ul>
<h2 id="references">References</h2>
<ul>
<li>https://docs.aws.amazon.com/lambda/latest/dg/lambda-nodejs.html</li>
<li>https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Lambda.html#invoke-property</li>
</ul>Anjaneyulu BattaWhen we have multiple lambda functions which are dependent on one another then we may need the execution output of another lambda function in the current lambda function inorder to process it. In such cases, we need to call/invoke the dependent lambda function from the current lambda function. Let’s say we have two lambda functions process PDF contents - process_pdf_invoice update PDF contents in the database - save_invoice_info We will be using nodejs to implement this scenario. Function - process_pdf_invoice Let’s write the code for process_pdf_invoice lambda function. process_pdf_invoice.js exports.handler = function(event, context) { // Let's return some dummy data const invoice = { "DueDate": "2013-02-15", "Balance": 1990.19, "DocNumber": "SAMP001", "Status": "Payable", "Line": [ { "Description": "Sample Expense", "Amount": 500, "DetailType": "ExpenseDetail", "Customer": "ABC123 (Sample Customer)", "Ref": "DEF234 (Sample Construction)", "Account": "EFG345 (Fuel)", "LineStatus": "Billable" } ], "TotalAmt": 1990.19 } return invoice } Function - save_invoice_info Let’s write the code for lambda function save_invoice_info To invoke the lambda function we need AWS SDK (i.e aws-sdk). By default it will be available in AWS lambda if not we need to install it as a layer. save_invoice_info.js const AWS = require('aws-sdk'); const lambda = new AWS.Lambda(); async function getInvoiceInfo(){ // params to send to lambda const params = { FunctionName: 'process_pdf_invoice', InvocationType: 'RequestResponse', LogType: 'None', Payload: '{}', }; const response = await lambda.invoke(params).promise(); if(response.StatusCode !== 200){ throw new Error('Failed to get response from lambda function') } return JSON.parse(response.Payload); } exports.handler = async function(event, context) { // invoke and get info from `process_pdf_invoice` const invoice = await getInvoiceInfo(); console.log('invoice', invoice); // now write the code to save data into database return {'status': 'saved'} } The code lambda.invoke(params).promise() will invoke lambda function and returns the response. If the invokation is success then it will return 200 otherwise it will return 5XX code. response.Payload will give the response returned by the lambda function. References https://docs.aws.amazon.com/lambda/latest/dg/lambda-nodejs.html https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Lambda.html#invoke-propertyServerless cronjobs with AWS Lambda and Events Rule2022-02-21T00:00:00+00:002022-02-21T00:00:00+00:00https://agiliq.com/blog/2022/02/serverless-cronjobs-with-lambda-events-rule<p>In many cases we may need to run the lambda function on a specific schedule. To achieve this on AWS cloud we need the following resources.</p>
<ul>
<li>Lambda Function</li>
<li>CloudWatch Events Rule</li>
<li>Lambda Permission</li>
</ul>
<p>As we will be using the serverless framework to configure the cron based lambda function we need to have basic knowledge of how serverless framework works. When we deploy the resources via serverless framework it creates the AWS CloudFormation template and create a zip file and sends it to the AWS S3 and then starts the deployment.</p>
<p>Note: AWS user need to have access to resources <strong><a href="https://aws.amazon.com/s3/">S3 Bucket</a></strong>, <strong><a href="https://aws.amazon.com/iam/">IAM Role</a></strong></p>
<h2 id="s3-bucket">S3 Bucket</h2>
<ul>
<li>It will be used by the serverless framework to upload the deployment package <code class="highlighter-rouge">.zip</code> file</li>
</ul>
<h2 id="iam-role">IAM Role</h2>
<ul>
<li>As with almost everything on AWS we’d need to manage permissions for our Lambda function with IAM. At the very least it should be able to write logs, so it needs access to CloudWatch Logs.</li>
</ul>
<h2 id="lambda-function">Lambda Function</h2>
<ul>
<li>It will execute the required functionality when it get’s triggered by the events rule or any other AWS resource.</li>
</ul>
<h2 id="cloudwatch-events-rule">CloudWatch Events Rule</h2>
<ul>
<li>CloudWatch Events supports <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html">cron-like expressions</a> which we can use to define how often an event is created.</li>
</ul>
<h2 id="lambda-permission">Lambda Permission</h2>
<ul>
<li>Unfortunately, we can’t trigger the lambda function from CloudWatch Events Rule unless we give lambda execution permission to <em>CloudWatch Events Rule</em></li>
<li>Anything that wants to invoke a Lambda function needs to have explicit permission to do that.</li>
</ul>
<h2 id="setting-things-up">Setting things up</h2>
<p>Let’s make our hands dirty by creating the above use case that we discussed.</p>
<h3 id="create-the-lambda-function---helloworld">create the lambda function - helloWorld</h3>
<blockquote>
<p>handler.js</p>
</blockquote>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">helloWorld</span> <span class="o">=</span> <span class="p">(</span><span class="nx">event</span><span class="p">,</span> <span class="nx">context</span><span class="p">,</span> <span class="nx">callback</span><span class="p">)</span> <span class="o">=></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="s2">"Hello, world!"</span><span class="p">)</span>
<span class="nx">callback</span><span class="p">(</span><span class="kc">null</span><span class="p">)</span>
<span class="p">}</span>
<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span><span class="nx">helloWorld</span><span class="p">}</span>
</code></pre></div></div>
<h3 id="configure-the-serverlessyml">configure the serverless.yml</h3>
<p>Let’s create the <code class="highlighter-rouge">serverless.yml</code> and write the configuration.</p>
<blockquote>
<p>serverless.yml</p>
</blockquote>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">service</span><span class="pi">:</span> <span class="s">cronjob-service</span>
<span class="na">provider</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">aws</span>
<span class="na">region</span><span class="pi">:</span> <span class="s">us-west-2</span>
<span class="na">runtime</span><span class="pi">:</span> <span class="s">nodejs14.10</span>
<span class="na">functions</span><span class="pi">:</span>
<span class="na">hello</span><span class="pi">:</span>
<span class="na">handler</span><span class="pi">:</span> <span class="s">handler.helloWorld</span>
<span class="na">events</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">schedule</span><span class="pi">:</span>
<span class="na">rate</span><span class="pi">:</span> <span class="s">cron(*/5 * * * ? *)</span>
<span class="na">enabled</span><span class="pi">:</span> <span class="no">true</span>
</code></pre></div></div>
<p>Other way to define it manually is like below</p>
<blockquote>
<p>serverless.yml</p>
</blockquote>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">service</span><span class="pi">:</span> <span class="s">cronjob-service</span>
<span class="na">provider</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">aws</span>
<span class="na">region</span><span class="pi">:</span> <span class="s">us-west-2</span>
<span class="na">runtime</span><span class="pi">:</span> <span class="s">nodejs14.10</span>
<span class="na">functions</span><span class="pi">:</span>
<span class="na">helloWorld</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">helloWorld</span>
<span class="na">handler</span><span class="pi">:</span> <span class="s">handler.helloWorld</span>
<span class="na">resources</span><span class="pi">:</span>
<span class="na">Resources</span><span class="pi">:</span>
<span class="na">CronJobTrigger</span><span class="pi">:</span>
<span class="na">Type</span><span class="pi">:</span> <span class="s">AWS::Events::Rule</span>
<span class="na">Properties</span><span class="pi">:</span>
<span class="na">Name</span><span class="pi">:</span> <span class="s">CronJobTrigger</span>
<span class="na">Description</span><span class="pi">:</span> <span class="s">trigger every 5 min</span>
<span class="na">ScheduleExpression</span><span class="pi">:</span> <span class="s">cron(*/5 * * * ? *)</span>
<span class="na">State</span><span class="pi">:</span> <span class="s">ENABLED</span>
<span class="na">Targets</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">Id</span><span class="pi">:</span> <span class="s">CronJobTrigger</span>
<span class="na">Arn</span><span class="pi">:</span> <span class="s">arn:aws:lambda:us-west-2:${aws:accountId}:function:helloWorld</span>
<span class="na">LambdaInvokePermission</span><span class="pi">:</span>
<span class="na">Type</span><span class="pi">:</span> <span class="s">AWS::Lambda::Permission</span>
<span class="na">Properties</span><span class="pi">:</span>
<span class="na">FunctionName</span><span class="pi">:</span> <span class="s">helloWorld</span>
<span class="na">Action</span><span class="pi">:</span> <span class="s">lambda:InvokeFunction</span>
<span class="na">Principal</span><span class="pi">:</span> <span class="s">events.amazonaws.com</span>
<span class="na">SourceArn</span><span class="pi">:</span>
<span class="s">Fn::GetAtt</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">CronJobTrigger</span><span class="pi">,</span> <span class="nv">Arn</span><span class="pi">]</span>
<span class="na">DependsOn</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">HelloWorldLambdaFunction</span> <span class="c1"># {title case of lambda function name}LambdaFunction</span>
</code></pre></div></div>
<p>Above configuration will trigger the lambda for every 5 min. Now, let’s deploy it with serverless framework. To deploy it run the below command</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>serverless deploy
</code></pre></div></div>
<p>After successful deployment. Let’s debug the lambda function by checking it’s logs. Let’s see the logs with command <code class="highlighter-rouge">serverless logs --function helloWorld --tail</code></p>
<p>It will give the logs something like below.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">START</span> <span class="nx">RequestId</span><span class="p">:</span> <span class="nx">bb2cc533</span><span class="o">-</span><span class="mi">11</span><span class="nx">e7</span><span class="o">-</span><span class="nx">a86d</span><span class="o">-</span><span class="nx">a3f3</span><span class="o">-</span><span class="mi">5</span><span class="nx">ba627dcc6d6</span> <span class="nx">Version</span><span class="p">:</span> <span class="nx">$LATEST</span>
<span class="mi">2022</span><span class="o">-</span><span class="mi">02</span><span class="o">-</span><span class="mi">21</span> <span class="mi">14</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mf">22.173</span> <span class="p">(</span><span class="o">+</span><span class="mi">02</span><span class="p">:</span><span class="mi">00</span><span class="p">)</span> <span class="nx">bb2cc533</span><span class="o">-</span><span class="mi">11</span><span class="nx">e7</span><span class="o">-</span><span class="nx">a86d</span><span class="o">-</span><span class="nx">a3f3</span><span class="o">-</span><span class="mi">5</span><span class="nx">ba627dcc6d6</span> <span class="nx">Hello</span><span class="p">,</span> <span class="nx">world</span><span class="o">!</span>
<span class="nx">END</span> <span class="nx">RequestId</span><span class="p">:</span> <span class="nx">bb2cc533</span><span class="o">-</span><span class="mi">11</span><span class="nx">e7</span><span class="o">-</span><span class="nx">a86d</span><span class="o">-</span><span class="nx">a3f3</span><span class="o">-</span><span class="mi">5</span><span class="nx">ba627dcc6d6</span>
<span class="nx">REPORT</span> <span class="nx">RequestId</span><span class="p">:</span> <span class="nx">bb2cc533</span><span class="o">-</span><span class="mi">11</span><span class="nx">e7</span><span class="o">-</span><span class="nx">a86d</span><span class="o">-</span><span class="nx">a3f3</span><span class="o">-</span><span class="mi">5</span><span class="nx">ba627dcc6d6</span> <span class="nx">Duration</span><span class="p">:</span> <span class="mf">2.50</span> <span class="nx">ms</span> <span class="nx">Billed</span> <span class="nx">Duration</span><span class="p">:</span> <span class="mi">100</span> <span class="nx">ms</span>
</code></pre></div></div>
<p>After verification remove the cloudformation stack with command <code class="highlighter-rouge">serverless remove</code>.</p>
<p>References:</p>
<ul>
<li>https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html</li>
<li>https://www.serverless.com/framework/docs/providers/aws/events/schedule</li>
</ul>Anjaneyulu BattaIn many cases we may need to run the lambda function on a specific schedule. To achieve this on AWS cloud we need the following resources. Lambda Function CloudWatch Events Rule Lambda Permission As we will be using the serverless framework to configure the cron based lambda function we need to have basic knowledge of how serverless framework works. When we deploy the resources via serverless framework it creates the AWS CloudFormation template and create a zip file and sends it to the AWS S3 and then starts the deployment. Note: AWS user need to have access to resources S3 Bucket, IAM Role S3 Bucket It will be used by the serverless framework to upload the deployment package .zip file IAM Role As with almost everything on AWS we’d need to manage permissions for our Lambda function with IAM. At the very least it should be able to write logs, so it needs access to CloudWatch Logs. Lambda Function It will execute the required functionality when it get’s triggered by the events rule or any other AWS resource. CloudWatch Events Rule CloudWatch Events supports cron-like expressions which we can use to define how often an event is created. Lambda Permission Unfortunately, we can’t trigger the lambda function from CloudWatch Events Rule unless we give lambda execution permission to CloudWatch Events Rule Anything that wants to invoke a Lambda function needs to have explicit permission to do that. Setting things up Let’s make our hands dirty by creating the above use case that we discussed. create the lambda function - helloWorld handler.js const helloWorld = (event, context, callback) => { console.log("Hello, world!") callback(null) } module.exports = {helloWorld} configure the serverless.yml Let’s create the serverless.yml and write the configuration. serverless.yml service: cronjob-service provider: name: aws region: us-west-2 runtime: nodejs14.10 functions: hello: handler: handler.helloWorld events: - schedule: rate: cron(*/5 * * * ? *) enabled: true Other way to define it manually is like below serverless.yml service: cronjob-service provider: name: aws region: us-west-2 runtime: nodejs14.10 functions: helloWorld: name: helloWorld handler: handler.helloWorld resources: Resources: CronJobTrigger: Type: AWS::Events::Rule Properties: Name: CronJobTrigger Description: trigger every 5 min ScheduleExpression: cron(*/5 * * * ? *) State: ENABLED Targets: - Id: CronJobTrigger Arn: arn:aws:lambda:us-west-2:${aws:accountId}:function:helloWorld LambdaInvokePermission: Type: AWS::Lambda::Permission Properties: FunctionName: helloWorld Action: lambda:InvokeFunction Principal: events.amazonaws.com SourceArn: Fn::GetAtt: [CronJobTrigger, Arn] DependsOn: - HelloWorldLambdaFunction # {title case of lambda function name}LambdaFunction Above configuration will trigger the lambda for every 5 min. Now, let’s deploy it with serverless framework. To deploy it run the below command serverless deploy After successful deployment. Let’s debug the lambda function by checking it’s logs. Let’s see the logs with command serverless logs --function helloWorld --tail It will give the logs something like below. START RequestId: bb2cc533-11e7-a86d-a3f3-5ba627dcc6d6 Version: $LATEST 2022-02-21 14:00:22.173 (+02:00) bb2cc533-11e7-a86d-a3f3-5ba627dcc6d6 Hello, world! END RequestId: bb2cc533-11e7-a86d-a3f3-5ba627dcc6d6 REPORT RequestId: bb2cc533-11e7-a86d-a3f3-5ba627dcc6d6 Duration: 2.50 ms Billed Duration: 100 ms After verification remove the cloudformation stack with command serverless remove. References: https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html https://www.serverless.com/framework/docs/providers/aws/events/scheduleCreating concurrent indexes without down time in Django + PostgreSQL2021-07-31T00:00:00+00:002021-07-31T00:00:00+00:00https://agiliq.com/blog/2021/07/django-concurrent-indexes-without-down-time<h2 id="why-to-add-indexes">Why to add indexes?</h2>
<ul>
<li>We add indexes on database table columns to speed up the select query as indexes allow faster lookup.</li>
<li>Adding indexes on multiple columns will slow down the write query, so we need to add indexes only on required columns.</li>
</ul>
<h2 id="why-do-we-face-downtime-when-adding-indexes">Why do we face downtime when adding indexes?</h2>
<ul>
<li>In most of the databases, adding an index requires an exclusive lock on the table.</li>
<li>Because, we don’t want to execute any <code class="highlighter-rouge">UPDATE</code>, <code class="highlighter-rouge">INSERT</code>, and <code class="highlighter-rouge">DELETE</code> operations while the index is created.</li>
<li>Locking a table might create a problem if the system needs to be available when creating the indexes. If the table has more data, then it will take more time to create the indexes.</li>
<li>As a result, the system will be unavailable till the index creation process completes.</li>
</ul>
<h2 id="what-can-be-done-to-prevent-the-database-unavailability-when-adding-indexes">What can be done to prevent the database unavailability when adding indexes?</h2>
<ul>
<li>If the database vendors didn’t provide the functionality to create the indexes without locking the table then we can’t do anything.</li>
<li>But, some database vendors like PostgreSQL provide the functionality to create the indexes without locking the table.</li>
<li>We can create indexes on PostgreSQL concurrently using keyword <code class="highlighter-rouge">CONCURRENTLY</code>.</li>
<li>Let’s see a simple SQL query to create an index concurrently.</li>
</ul>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">INDEX</span> <span class="n">CONCURRENTLY</span> <span class="nv">"my_index"</span> <span class="k">ON</span> <span class="n">my_table</span><span class="p">(</span><span class="nv">"my_column"</span><span class="p">);</span>
</code></pre></div></div>
<h2 id="django-migration-to-apply-indexes-concurrently-on-a-postgresql-database">Django migration to apply indexes concurrently on a PostgreSQL database</h2>
<p>Let’s consider a simple scenario of product table.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">django.db</span> <span class="kn">import</span> <span class="n">models</span>
<span class="k">class</span> <span class="nc">Product</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
<span class="n">name</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">()</span>
<span class="n">description</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">TextField</span><span class="p">()</span>
<span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span>
<span class="n">db_table</span><span class="o">=</span><span class="s">"product"</span>
</code></pre></div></div>
<p>Let’s say we didn’t add the indexes on the table and the data is on the table is huge and it’s resulting in slow queries. so, we need to create the indexes for the column <code class="highlighter-rouge">name</code>.</p>
<p>By defult django runs the migrations by acquiring the lock on the table. To avoid it, we need to write a custom migration.</p>
<p>Let’s create a migration to create indexes for the table concurrently.</p>
<blockquote>
<p>002_product_index.py</p>
</blockquote>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">django.contrib.postgres.indexes</span>
<span class="kn">from</span> <span class="nn">django.db</span> <span class="kn">import</span> <span class="n">migrations</span><span class="p">,</span> <span class="n">models</span>
<span class="kn">from</span> <span class="nn">django.contrib.postgres.operations</span> <span class="kn">import</span> <span class="n">AddIndexConcurrently</span>
<span class="k">class</span> <span class="nc">Migration</span><span class="p">(</span><span class="n">migrations</span><span class="o">.</span><span class="n">Migration</span><span class="p">):</span>
<span class="n">atomic</span> <span class="o">=</span> <span class="bp">False</span>
<span class="n">dependencies</span> <span class="o">=</span> <span class="p">[</span>
<span class="p">(</span><span class="s">"app_name"</span><span class="p">,</span> <span class="s">"001_initial"</span><span class="p">),</span>
<span class="p">]</span>
<span class="n">operations</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">AddIndexConcurrently</span><span class="p">(</span>
<span class="n">model_name</span><span class="o">=</span><span class="s">"product"</span><span class="p">,</span>
<span class="n">index</span><span class="o">=</span><span class="n">models</span><span class="o">.</span><span class="n">Index</span><span class="p">(</span>
<span class="n">fields</span><span class="o">=</span><span class="p">[</span><span class="s">"name"</span><span class="p">],</span> <span class="n">name</span><span class="o">=</span><span class="s">"name_idx"</span>
<span class="p">),</span>
<span class="p">),</span>
<span class="p">]</span>
</code></pre></div></div>
<ul>
<li>When we run the concurrent migrations on django we need to set <code class="highlighter-rouge">atomic = False</code> on the migration file.</li>
<li>To create the index concurrently on PostgreSQL django ORM provides class <code class="highlighter-rouge">AddIndexConcurrently</code></li>
<li>By using <code class="highlighter-rouge">AddIndexConcurrently</code> and <code class="highlighter-rouge">atomic=False</code> we can create the indexes concurrently.</li>
</ul>
<p>Let’s create a SQL for the above migration to confirm the SQL</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python manage.py sqlmigrate app_name 002_product_index
</code></pre></div></div>
<p>It will generate the SQL like below</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">INDEX</span> <span class="n">CONCURRENTLY</span> <span class="nv">"name_idx"</span> <span class="k">ON</span> <span class="n">product</span><span class="p">(</span><span class="nv">"name"</span><span class="p">);</span>
</code></pre></div></div>
<p>Now, we can confirm that the migration will create the indexes concurrently.
Let’s apply the migration with the below command</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python manage.py migrate app_name 002_product_index
</code></pre></div></div>
<p>Now, check the database. We should be able to see the index created for the table.</p>
<p>That’s it folks. stay tuned for more articles.</p>
<p>References:</p>
<ol>
<li>https://docs.djangoproject.com/en/dev/ref/contrib/postgres/operations/#django.contrib.postgres.operations.AddIndexConcurrently</li>
<li>https://docs.djangoproject.com/en/3.2/howto/writing-migrations/#non-atomic-migrations</li>
<li>https://www.postgresql.org/docs/9.1/sql-createindex.html</li>
</ol>Anjaneyulu BattaWhy to add indexes? We add indexes on database table columns to speed up the select query as indexes allow faster lookup. Adding indexes on multiple columns will slow down the write query, so we need to add indexes only on required columns. Why do we face downtime when adding indexes? In most of the databases, adding an index requires an exclusive lock on the table. Because, we don’t want to execute any UPDATE, INSERT, and DELETE operations while the index is created. Locking a table might create a problem if the system needs to be available when creating the indexes. If the table has more data, then it will take more time to create the indexes. As a result, the system will be unavailable till the index creation process completes. What can be done to prevent the database unavailability when adding indexes? If the database vendors didn’t provide the functionality to create the indexes without locking the table then we can’t do anything. But, some database vendors like PostgreSQL provide the functionality to create the indexes without locking the table. We can create indexes on PostgreSQL concurrently using keyword CONCURRENTLY. Let’s see a simple SQL query to create an index concurrently. CREATE INDEX CONCURRENTLY "my_index" ON my_table("my_column"); Django migration to apply indexes concurrently on a PostgreSQL database Let’s consider a simple scenario of product table. from django.db import models class Product(models.Model): name = models.CharField() description = models.TextField() class Meta: db_table="product" Let’s say we didn’t add the indexes on the table and the data is on the table is huge and it’s resulting in slow queries. so, we need to create the indexes for the column name. By defult django runs the migrations by acquiring the lock on the table. To avoid it, we need to write a custom migration. Let’s create a migration to create indexes for the table concurrently. 002_product_index.py import django.contrib.postgres.indexes from django.db import migrations, models from django.contrib.postgres.operations import AddIndexConcurrently class Migration(migrations.Migration): atomic = False dependencies = [ ("app_name", "001_initial"), ] operations = [ AddIndexConcurrently( model_name="product", index=models.Index( fields=["name"], name="name_idx" ), ), ] When we run the concurrent migrations on django we need to set atomic = False on the migration file. To create the index concurrently on PostgreSQL django ORM provides class AddIndexConcurrently By using AddIndexConcurrently and atomic=False we can create the indexes concurrently. Let’s create a SQL for the above migration to confirm the SQL python manage.py sqlmigrate app_name 002_product_index It will generate the SQL like below CREATE INDEX CONCURRENTLY "name_idx" ON product("name"); Now, we can confirm that the migration will create the indexes concurrently. Let’s apply the migration with the below command python manage.py migrate app_name 002_product_index Now, check the database. We should be able to see the index created for the table. That’s it folks. stay tuned for more articles. References: https://docs.djangoproject.com/en/dev/ref/contrib/postgres/operations/#django.contrib.postgres.operations.AddIndexConcurrently https://docs.djangoproject.com/en/3.2/howto/writing-migrations/#non-atomic-migrations https://www.postgresql.org/docs/9.1/sql-createindex.htmlDjango application monitoring with datadog2021-07-09T00:00:00+00:002021-07-09T00:00:00+00:00https://agiliq.com/blog/2021/07/django-apm-with-datadog<h2 id="application-performance-monitoring">Application performance monitoring</h2>
<p>Application performance monitoring (i.e APM) is a way to analyze the each transaction of an application and find the pitfalls of it.</p>
<p>APM allow us to find</p>
<ul>
<li>Whether the app is behaving as expected or not?</li>
<li>What parts of the application is using more resources like Memory, CPU, etc.?</li>
<li>What queries are taking more time in database?</li>
<li>How frequently an url is being requested?</li>
<li>Application availability</li>
</ul>
<p>With the above information, we can fix the issues and improve the application before it affects the business.</p>
<h2 id="datadog-and-its-features">Datadog and it’s features</h2>
<p>Datadog is a monitoring service for cloud-scale applications, providing monitoring of servers, databases, tools, and services, through a SaaS-based data analytics platform.</p>
<p>Features</p>
<ul>
<li>Allows developer to analyze each request in depth, so that developer can see what part of the application is taking more time.</li>
<li>It gives the insights on how frequently an API or web page is failing or accessing</li>
<li>It allows to set the <em>alerts</em> so that developers can be notified if anything goes wrong.</li>
<li>It allows clear insights on database queries so that dev’s can analyze the issue and fix/improve the DB/Query performance.</li>
<li>It allows to create the dashboards for different application events and create alerts based on the events. so that people will know before it impact the business.</li>
<li>It supports different languages and their frameworks also.</li>
</ul>
<h2 id="integrating-datadog-with-django-app">Integrating Datadog with django app</h2>
<p>Get the API key and install the datadog agent with below command</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">DD_API_KEY</span><span class="o">=</span><API_KEY> <span class="nv">DD_AGENT_MAJOR_VERSION</span><span class="o">=</span>7 bash <span class="nt">-c</span> <span class="s2">"</span><span class="k">$(</span>curl <span class="nt">-L</span> https://raw.githubusercontent.com/DataDog/datadog-agent/master/cmd/agent/install_script.sh<span class="k">)</span><span class="s2">"</span>
</code></pre></div></div>
<p>To use the datadog with django we need to install the python package <code class="highlighter-rouge">ddtrace</code> with below command.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">pip</span> <span class="n">install</span> <span class="n">ddtrace</span>
</code></pre></div></div>
<p>Now, run the <code class="highlighter-rouge">ddtrace-run</code> wrapper with your <code class="highlighter-rouge">gunicorn</code> command, keeping all the arguments intact:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ddtrace-run gunicorn <span class="o">[</span>...] myapp.wsgi:application <span class="o">[</span>...]
</code></pre></div></div>
<p>Update the <code class="highlighter-rouge">settings.py</code> file by adding <code class="highlighter-rouge">'ddtrace.contrib.django'</code> to installed apps.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">INSTALLED_APPS</span> <span class="o">=</span> <span class="p">[</span>
<span class="c"># your Django apps...</span>
<span class="s">'ddtrace.contrib.django'</span><span class="p">,</span>
<span class="p">]</span>
</code></pre></div></div>
<p>Restart the application and send few requests to the app, we should be able to see the metrics of django application.</p>
<h2 id="integrating-datadog-with-postgresql">Integrating datadog with PostgreSQL</h2>
<p>SSH into PostgreSQL server and install the datadog agent with below command. You will need to get the API Key</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">DD_API_KEY</span><span class="o">=</span><API_KEY> <span class="nv">DD_AGENT_MAJOR_VERSION</span><span class="o">=</span>7 bash <span class="nt">-c</span> <span class="s2">"</span><span class="k">$(</span>curl <span class="nt">-L</span> https://raw.githubusercontent.com/DataDog/datadog-agent/master/cmd/agent/install_script.sh<span class="k">)</span><span class="s2">"</span>
</code></pre></div></div>
<p>Now, create a postgres user <code class="highlighter-rouge">datadog</code> with read-only access.</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">create</span> <span class="k">user</span> <span class="n">datadog</span> <span class="k">with</span> <span class="n">password</span> <span class="s1">'<PASSWORD>'</span><span class="p">;</span>
<span class="k">grant</span> <span class="n">pg_monitor</span> <span class="k">to</span> <span class="n">datadog</span><span class="p">;</span>
<span class="k">grant</span> <span class="k">SELECT</span> <span class="k">ON</span> <span class="n">pg_stat_database</span> <span class="k">to</span> <span class="n">datadog</span><span class="p">;</span>
</code></pre></div></div>
<p>Verify connection using below command</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">psql</span> <span class="o">-</span><span class="n">h</span> <span class="n">localhost</span> <span class="o">-</span><span class="n">U</span> <span class="n">datadog</span> <span class="n">postgres</span> <span class="o">-</span><span class="k">c</span> <span class="err">\</span>
<span class="nv">"select * from pg_stat_database LIMIT(1);"</span> <span class="err">\</span>
<span class="o">&&</span> <span class="n">echo</span> <span class="o">-</span><span class="n">e</span> <span class="nv">"</span><span class="se">\e</span><span class="nv">[0;32mPostgres connection - OK</span><span class="se">\e</span><span class="nv">[0m"</span> <span class="err">\</span>
<span class="o">||</span> <span class="n">echo</span> <span class="o">-</span><span class="n">e</span> <span class="nv">"</span><span class="se">\e</span><span class="nv">[0;31mCannot connect to Postgres</span><span class="se">\e</span><span class="nv">[0m"</span>
</code></pre></div></div>
<p>To configure the Agent to collect PostgreSQL metrics, create a <code class="highlighter-rouge">conf.yaml</code> file from the provided template.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>cp /etc/datadog-agent/conf.d/postgres.d/conf.yaml.example /etc/datadog-agent/conf.d/postgres.d/conf.yaml
</code></pre></div></div>
<p>Add the following to config file:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">init_config</span><span class="pi">:</span>
<span class="na">instances</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">host</span><span class="pi">:</span> <span class="s">localhost</span>
<span class="na">port</span><span class="pi">:</span> <span class="s">5432</span>
<span class="na">username</span><span class="pi">:</span> <span class="s">datadog</span>
<span class="na">password</span><span class="pi">:</span> <span class="s"><PASSWORD></span>
</code></pre></div></div>
<blockquote>
<p>Note: replace the localhost with actual host name or IP</p>
</blockquote>
<p>Restart the agent with below command</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo service datadog-agent restart
</code></pre></div></div>
<p>Wait for 5 min, We should be able to see PostgreSQL performance metrics in datadog dashboard.</p>
<p>That’s it folks. You can check their official docs at https://docs.datadoghq.com/</p>Anjaneyulu BattaApplication performance monitoring Application performance monitoring (i.e APM) is a way to analyze the each transaction of an application and find the pitfalls of it. APM allow us to find Whether the app is behaving as expected or not? What parts of the application is using more resources like Memory, CPU, etc.? What queries are taking more time in database? How frequently an url is being requested? Application availability With the above information, we can fix the issues and improve the application before it affects the business. Datadog and it’s features Datadog is a monitoring service for cloud-scale applications, providing monitoring of servers, databases, tools, and services, through a SaaS-based data analytics platform. Features Allows developer to analyze each request in depth, so that developer can see what part of the application is taking more time. It gives the insights on how frequently an API or web page is failing or accessing It allows to set the alerts so that developers can be notified if anything goes wrong. It allows clear insights on database queries so that dev’s can analyze the issue and fix/improve the DB/Query performance. It allows to create the dashboards for different application events and create alerts based on the events. so that people will know before it impact the business. It supports different languages and their frameworks also. Integrating Datadog with django app Get the API key and install the datadog agent with below command DD_API_KEY=<API_KEY> DD_AGENT_MAJOR_VERSION=7 bash -c "$(curl -L https://raw.githubusercontent.com/DataDog/datadog-agent/master/cmd/agent/install_script.sh)" To use the datadog with django we need to install the python package ddtrace with below command. pip install ddtrace Now, run the ddtrace-run wrapper with your gunicorn command, keeping all the arguments intact: ddtrace-run gunicorn [...] myapp.wsgi:application [...] Update the settings.py file by adding 'ddtrace.contrib.django' to installed apps. INSTALLED_APPS = [ # your Django apps... 'ddtrace.contrib.django', ] Restart the application and send few requests to the app, we should be able to see the metrics of django application. Integrating datadog with PostgreSQL SSH into PostgreSQL server and install the datadog agent with below command. You will need to get the API Key DD_API_KEY=<API_KEY> DD_AGENT_MAJOR_VERSION=7 bash -c "$(curl -L https://raw.githubusercontent.com/DataDog/datadog-agent/master/cmd/agent/install_script.sh)" Now, create a postgres user datadog with read-only access. create user datadog with password '<PASSWORD>'; grant pg_monitor to datadog; grant SELECT ON pg_stat_database to datadog; Verify connection using below command psql -h localhost -U datadog postgres -c \ "select * from pg_stat_database LIMIT(1);" \ && echo -e "\e[0;32mPostgres connection - OK\e[0m" \ || echo -e "\e[0;31mCannot connect to Postgres\e[0m" To configure the Agent to collect PostgreSQL metrics, create a conf.yaml file from the provided template. sudo cp /etc/datadog-agent/conf.d/postgres.d/conf.yaml.example /etc/datadog-agent/conf.d/postgres.d/conf.yaml Add the following to config file: init_config: instances: - host: localhost port: 5432 username: datadog password: <PASSWORD> Note: replace the localhost with actual host name or IP Restart the agent with below command sudo service datadog-agent restart Wait for 5 min, We should be able to see PostgreSQL performance metrics in datadog dashboard. That’s it folks. You can check their official docs at https://docs.datadoghq.com/DRF with Cognito Authentication2021-02-02T00:00:00+00:002021-02-02T00:00:00+00:00https://agiliq.com/blog/2021/02/cognito-with-drf<p>In this tutorial we’ll see how to integrate AWS Cognito together with Django Rest Framework. The end result would be that most of the details related to the user will be stored & managed by AWS cognito which reduces the hassle of managing users.</p>
<h2 id="setting-up-the-frontend-with-aws-amplify--cognito">Setting up the frontend (with AWS Amplify + Cognito)</h2>
<p>Lets first create a simple react app. Please make sure to install latest & stable version of <code class="highlighter-rouge">nodejs</code> and <code class="highlighter-rouge">npm</code></p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Create a new react app and cd into it</span>
npx create-react-app frontend_drf_cognito <span class="o">&&</span> <span class="nb">cd</span> <span class="nv">$_</span>
<span class="c"># Install AWS Amplify</span>
yarn add aws-amplify @aws-amplify/ui-react aws-amplify-react
<span class="c"># In another terminal/prompt, globally install AWS Amplify CLI</span>
npm install <span class="nt">-g</span> @aws-amplify/cli
</code></pre></div></div>
<p>Now that the initial setup is done, lets initialize AWS Amplify in our project root directory using the command <code class="highlighter-rouge">amplify init</code>. Give the details as per the image below
<img src="/assets/images/drf_cognito/amplify_init.png" alt="" />
We can also import existing amplify app using <code class="highlighter-rouge">amplify pull</code></p>
<p><code class="highlighter-rouge">Note:</code> Make sure that you have authentication module enabled in amplify. If not, just add it using <code class="highlighter-rouge">amplify add auth</code>.
<img src="/assets/images/drf_cognito/amplify_add_auth.png" alt="" />
Once done, run the command <code class="highlighter-rouge">amplify push</code> which will provision the required resources i.e. creating userpool, creating roles with required permissions, etc.</p>
<p>Now that cognito userpool has been created, open the file <code class="highlighter-rouge">src/aws-exports.js</code>. It should be something like this</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">...</span>
<span class="kd">const</span> <span class="nx">awsmobile</span> <span class="o">=</span> <span class="p">{</span>
<span class="p">...</span>
<span class="p">...</span>
<span class="p">...</span>
<span class="s2">"aws_user_pools_id"</span><span class="p">:</span> <span class="s2">"ap-southeast-1_XXXXXXXXX"</span><span class="p">,</span>
<span class="s2">"aws_user_pools_web_client_id"</span><span class="p">:</span> <span class="s2">"XXXXXXXXXXXXXXXXXXXXXXX"</span><span class="p">,</span>
<span class="p">...</span>
<span class="p">};</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">awsmobile</span><span class="p">;</span>
</code></pre></div></div>
<p>Make sure to take note of <code class="highlighter-rouge">aws_user_pools_id</code> and <code class="highlighter-rouge">aws_user_pools_web_client_id</code> as they’ll be used as environment variables in the django application. We’ll be setting them to the vars <code class="highlighter-rouge">AWS_APP_CLIENT_ID</code> and <code class="highlighter-rouge">AWS_USER_POOLS_ID</code></p>
<p>Finally start the frontend server using <code class="highlighter-rouge">yarn start</code></p>
<h2 id="setting-up-the-backend-django-application">Setting up the backend (Django Application)</h2>
<p>Lets look at some simple steps to setup a django project with drf. Also please run <code class="highlighter-rouge">cd ..</code> to make sure that we’re not mixing frontend and backend in the same directories. Its always better to keep both of them in different directories</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Setup virtualenv</span>
mkvirtualenv drf_cognito <span class="nt">-p</span> python3
<span class="c"># Selecting an existing virtual environment</span>
workon drf_cognito
<span class="c"># Installing django, drf and python-jose</span>
pip install django djangorestframework python-jose
<span class="c"># Initialize the project</span>
django-admin startproject drf_cognito
<span class="c"># Create a demo app</span>
django-admin startapp articles
<span class="c"># Better to have separate module for cognito auth</span>
django-admin startapp cognito
</code></pre></div></div>
<p>Once we’re ready with the app, lets add a few lines to settings. Also make sure to set the environment variables AWS_APP_CLIENT_ID and AWS_USER_POOLS_ID which were generated in cognito setup above.</p>
<p><code class="highlighter-rouge">backend_drf_cognito/settings.py</code></p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">...</span>
<span class="o">...</span>
<span class="o">...</span>
<span class="c"># Application definition</span>
<span class="n">INSTALLED_APPS</span> <span class="o">=</span> <span class="p">[</span>
<span class="o">...</span>
<span class="o">...</span>
<span class="s">"rest_framework"</span><span class="p">,</span>
<span class="p">]</span>
<span class="n">LOCAL_APPS</span> <span class="o">=</span> <span class="p">[</span>
<span class="s">"articles"</span><span class="p">,</span>
<span class="p">]</span>
<span class="n">INSTALLED_APPS</span> <span class="o">+=</span> <span class="n">LOCAL_APPS</span>
<span class="n">MIDDLEWARE</span> <span class="o">=</span> <span class="p">[</span>
<span class="o">...</span>
<span class="s">"django.contrib.auth.middleware.AuthenticationMiddleware"</span><span class="p">,</span>
<span class="s">"cognito.middleware.CognitoAuthMiddleware"</span><span class="p">,</span>
<span class="o">...</span>
<span class="p">]</span>
<span class="o">...</span>
<span class="o">...</span>
<span class="o">...</span>
<span class="c"># AWS Cognito Configuration</span>
<span class="n">COGNITO_CONFIG</span> <span class="o">=</span> <span class="p">{</span>
<span class="c"># URL consists of https://{pool-name}.auth.{region}.amazoncognito.com/oauth2/token</span>
<span class="c"># Replace it below accordingly</span>
<span class="s">"url"</span><span class="p">:</span> <span class="s">"https://frontenddrfcognito77b3bdee_userpool_77b3bdee-dev.auth.ap-southeast-2.amazoncognito.com/oauth2/token"</span><span class="p">,</span>
<span class="s">"app_client_id"</span><span class="p">:</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s">"AWS_APP_CLIENT_ID"</span><span class="p">],</span>
<span class="s">"region"</span><span class="p">:</span> <span class="s">"ap-southeast-2"</span><span class="p">,</span>
<span class="s">"aws_user_pools_id"</span><span class="p">:</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s">"AWS_USER_POOLS_ID"</span><span class="p">],</span>
<span class="s">"aws_user_pools_web_client_id"</span><span class="p">:</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s">"AWS_APP_CLIENT_ID"</span><span class="p">],</span>
<span class="p">}</span>
<span class="c"># Rest Framework settings</span>
<span class="n">REST_FRAMEWORK</span> <span class="o">=</span> <span class="p">{</span>
<span class="s">"DEFAULT_AUTHENTICATION_CLASSES"</span><span class="p">:</span> <span class="p">[</span>
<span class="s">"cognito.authentication.CognitoAuthentication"</span><span class="p">,</span>
<span class="p">],</span>
<span class="p">}</span>
</code></pre></div></div>
<p>For simplicity lets just have name of the article in the model.</p>
<p><code class="highlighter-rouge">articles/models.py</code></p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Default imports</span>
<span class="kn">from</span> <span class="nn">django.db</span> <span class="kn">import</span> <span class="n">models</span>
<span class="c"># Custom imports</span>
<span class="c"># None</span>
<span class="k">class</span> <span class="nc">Article</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
<span class="n">name</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">50</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-></span> <span class="nb">str</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span>
</code></pre></div></div>
<p>A serializer for articles</p>
<p><code class="highlighter-rouge">articles/serializers.py</code></p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Default imports</span>
<span class="kn">from</span> <span class="nn">rest_framework</span> <span class="kn">import</span> <span class="n">serializers</span>
<span class="c"># Custom imports</span>
<span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">Article</span>
<span class="k">class</span> <span class="nc">ArticleSerializer</span><span class="p">(</span><span class="n">serializers</span><span class="o">.</span><span class="n">ModelSerializer</span><span class="p">):</span>
<span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">Article</span>
<span class="n">fields</span> <span class="o">=</span> <span class="s">"__all__"</span>
</code></pre></div></div>
<p>Standard generics template for listing & creating articles using DRF</p>
<p><code class="highlighter-rouge">articles/views.py</code></p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Default imports</span>
<span class="kn">from</span> <span class="nn">rest_framework</span> <span class="kn">import</span> <span class="n">generics</span>
<span class="c"># Custom imports</span>
<span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">Article</span>
<span class="kn">from</span> <span class="nn">.serializers</span> <span class="kn">import</span> <span class="n">ArticleSerializer</span>
<span class="k">class</span> <span class="nc">ListCreateArticles</span><span class="p">(</span><span class="n">generics</span><span class="o">.</span><span class="n">ListCreateAPIView</span><span class="p">):</span>
<span class="n">serializer_class</span> <span class="o">=</span> <span class="n">ArticleSerializer</span>
<span class="n">queryset</span> <span class="o">=</span> <span class="n">Article</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="nb">all</span><span class="p">()</span>
</code></pre></div></div>
<p>URLs to navigate the the views created</p>
<p><code class="highlighter-rouge">articles/urls.py</code></p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Default imports</span>
<span class="kn">from</span> <span class="nn">django.urls</span> <span class="kn">import</span> <span class="n">path</span>
<span class="c"># Custom imports</span>
<span class="kn">from</span> <span class="nn">.views</span> <span class="kn">import</span> <span class="n">ListCreateArticles</span>
<span class="n">urlpatterns</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">path</span><span class="p">(</span><span class="s">""</span><span class="p">,</span> <span class="n">ListCreateArticles</span><span class="o">.</span><span class="n">as_view</span><span class="p">(),</span> <span class="n">name</span><span class="o">=</span><span class="s">"list_create_articles"</span><span class="p">),</span>
<span class="p">]</span>
</code></pre></div></div>
<p>Update project urls as well</p>
<p><code class="highlighter-rouge">drf_cognito_auth/urls.py</code></p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">django.contrib</span> <span class="kn">import</span> <span class="n">admin</span>
<span class="kn">from</span> <span class="nn">django.urls</span> <span class="kn">import</span> <span class="n">path</span><span class="p">,</span> <span class="n">include</span>
<span class="n">urlpatterns</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">path</span><span class="p">(</span><span class="s">"admin/"</span><span class="p">,</span> <span class="n">admin</span><span class="o">.</span><span class="n">site</span><span class="o">.</span><span class="n">urls</span><span class="p">),</span>
<span class="n">path</span><span class="p">(</span><span class="s">"articles/"</span><span class="p">,</span> <span class="n">include</span><span class="p">(</span><span class="s">"articles.urls"</span><span class="p">)),</span>
<span class="p">]</span>
</code></pre></div></div>
<p>Finally make migrations and migrate</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python manage.py makemigrations <span class="o">&&</span> python manage.py migrate
</code></pre></div></div>
<h2 id="integrating-drf-with-cognito">Integrating DRF with Cognito</h2>
<p>Now we need to integrate AWS Cognito as an authentication backend which will be internally used by django to recognise the user who is signing in and maintain their session when accessing any APIs.</p>
<p>So let’s setup a middleware</p>
<p><code class="highlighter-rouge">cognito/middleware.py</code></p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">...</span>
<span class="c"># Other imports</span>
<span class="o">...</span>
<span class="k">class</span> <span class="nc">CognitoAuthMiddleware</span><span class="p">(</span>
<span class="n">CognitoAuthenticationMixin</span><span class="p">,</span> <span class="n">middleware</span><span class="o">.</span><span class="n">AuthenticationMiddleware</span>
<span class="p">):</span>
<span class="nd">@staticmethod</span>
<span class="k">def</span> <span class="nf">get_auth_token</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">return</span> <span class="n">request</span><span class="o">.</span><span class="n">META</span><span class="p">[</span><span class="s">"HTTP_AUTHORIZATION"</span><span class="p">]</span>
<span class="k">except</span> <span class="nb">Exception</span><span class="p">:</span>
<span class="k">raise</span> <span class="n">NoAuthToken</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">process_request</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">):</span>
<span class="k">if</span> <span class="n">request</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="n">reverse</span><span class="p">(</span><span class="s">"admin:index"</span><span class="p">)):</span>
<span class="k">return</span> <span class="bp">None</span>
<span class="n">request</span><span class="o">.</span><span class="n">user</span> <span class="o">=</span> <span class="n">SimpleLazyObject</span><span class="p">(</span><span class="k">lambda</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">authenticate</span><span class="p">(</span><span class="n">request</span><span class="p">))</span>
</code></pre></div></div>
<p>For easy code reading, we have written a separate mixin for cognito authentication</p>
<p><code class="highlighter-rouge">cognito/authentication.py</code></p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">...</span>
<span class="c"># Other imports</span>
<span class="kn">from</span> <span class="nn">.</span> <span class="kn">import</span> <span class="n">keys</span>
<span class="c"># Make sure to have the keys array in the __init__.py file</span>
<span class="c"># in the cognito folder</span>
<span class="c"># Standard Format:</span>
<span class="c"># keys = [{...},{...}]</span>
<span class="c"># In order to get the keys data, visit</span>
<span class="c"># https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json</span>
<span class="c"># Replace region and userPoolId with respective values</span>
<span class="o">...</span>
<span class="k">def</span> <span class="nf">get_jwt_claims</span><span class="p">(</span><span class="n">token</span><span class="p">):</span>
<span class="c"># get the kid from the headers prior to verification</span>
<span class="n">headers</span> <span class="o">=</span> <span class="n">jwt</span><span class="o">.</span><span class="n">get_unverified_headers</span><span class="p">(</span><span class="n">token</span><span class="p">)</span>
<span class="n">kid</span> <span class="o">=</span> <span class="n">headers</span><span class="p">[</span><span class="s">"kid"</span><span class="p">]</span>
<span class="c"># search for the kid in the downloaded public keys</span>
<span class="n">key_index</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">keys</span><span class="p">)):</span>
<span class="k">if</span> <span class="n">kid</span> <span class="o">==</span> <span class="n">keys</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="s">"kid"</span><span class="p">]:</span>
<span class="n">key_index</span> <span class="o">=</span> <span class="n">i</span>
<span class="k">break</span>
<span class="k">if</span> <span class="n">key_index</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">:</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Public key not found in jwks.json"</span><span class="p">)</span>
<span class="k">return</span> <span class="p">[]</span>
<span class="c"># construct the public key</span>
<span class="n">public_key</span> <span class="o">=</span> <span class="n">jwk</span><span class="o">.</span><span class="n">construct</span><span class="p">(</span><span class="n">keys</span><span class="p">[</span><span class="n">key_index</span><span class="p">])</span>
<span class="c"># get the last two sections of the token,</span>
<span class="c"># message and signature (encoded in base64)</span>
<span class="n">message</span><span class="p">,</span> <span class="n">encoded_signature</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">token</span><span class="p">)</span><span class="o">.</span><span class="n">rsplit</span><span class="p">(</span><span class="s">"."</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="c"># decode the signature</span>
<span class="n">decoded_signature</span> <span class="o">=</span> <span class="n">base64url_decode</span><span class="p">(</span><span class="n">encoded_signature</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s">"utf-8"</span><span class="p">))</span>
<span class="c"># verify the signature</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">public_key</span><span class="o">.</span><span class="n">verify</span><span class="p">(</span><span class="n">message</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s">"utf8"</span><span class="p">),</span> <span class="n">decoded_signature</span><span class="p">):</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Signature verification failed"</span><span class="p">)</span>
<span class="k">return</span> <span class="p">[]</span>
<span class="c"># print("Signature successfully verified")</span>
<span class="c"># since we passed the verification, we can now safely</span>
<span class="c"># use the unverified claims</span>
<span class="n">claims</span> <span class="o">=</span> <span class="n">jwt</span><span class="o">.</span><span class="n">get_unverified_claims</span><span class="p">(</span><span class="n">token</span><span class="p">)</span>
<span class="n">ts</span> <span class="o">=</span> <span class="n">claims</span><span class="p">[</span><span class="s">"exp"</span><span class="p">]</span>
<span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s">"TZ"</span><span class="p">]</span> <span class="o">=</span> <span class="s">"Asia/Kolkata"</span>
<span class="n">time</span><span class="o">.</span><span class="n">tzset</span><span class="p">()</span>
<span class="k">print</span><span class="p">(</span>
<span class="s">"Current Expiry of Token : {}"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span>
<span class="n">time</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s">"</span><span class="si">%</span><span class="s">Y-</span><span class="si">%</span><span class="s">m-</span><span class="si">%</span><span class="s">d </span><span class="si">%</span><span class="s">H:</span><span class="si">%</span><span class="s">M:</span><span class="si">%</span><span class="s">S"</span><span class="p">,</span> <span class="n">time</span><span class="o">.</span><span class="n">localtime</span><span class="p">(</span><span class="n">ts</span><span class="p">))</span>
<span class="p">)</span>
<span class="p">)</span>
<span class="c"># Checking token expiry</span>
<span class="k">if</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">></span> <span class="n">claims</span><span class="p">[</span><span class="s">"exp"</span><span class="p">]:</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Token is expired"</span><span class="p">)</span>
<span class="k">return</span> <span class="p">[]</span>
<span class="k">if</span> <span class="n">claims</span><span class="p">[</span><span class="s">"aud"</span><span class="p">]</span> <span class="o">!=</span> <span class="n">settings</span><span class="o">.</span><span class="n">COGNITO_CONFIG</span><span class="p">[</span><span class="s">"app_client_id"</span><span class="p">]:</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Token was not issued for this audience"</span><span class="p">)</span>
<span class="k">return</span> <span class="p">[]</span>
<span class="k">return</span> <span class="n">claims</span>
<span class="k">class</span> <span class="nc">CognitoAuthenticationMixin</span><span class="p">:</span>
<span class="nd">@staticmethod</span>
<span class="k">def</span> <span class="nf">get_auth_token</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="k">raise</span> <span class="nb">NotImplementedError</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">authenticate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">):</span>
<span class="n">token</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_auth_token</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">claims</span> <span class="o">=</span> <span class="n">get_jwt_claims</span><span class="p">(</span><span class="n">token</span><span class="p">)</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">claims</span><span class="p">)</span> <span class="o">></span> <span class="mi">0</span><span class="p">:</span>
<span class="n">user</span> <span class="o">=</span> <span class="n">UserModel</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">email</span><span class="o">=</span><span class="n">claims</span><span class="p">[</span><span class="s">"email"</span><span class="p">])</span>
<span class="k">return</span> <span class="n">user</span>
<span class="k">raise</span> <span class="n">NoSuchClaims</span><span class="p">()</span>
<span class="k">except</span> <span class="n">UserModel</span><span class="o">.</span><span class="n">DoesNotExist</span><span class="p">:</span>
<span class="k">raise</span> <span class="n">NoSuchUser</span><span class="p">()</span>
<span class="k">except</span> <span class="nb">Exception</span><span class="p">:</span>
<span class="k">raise</span> <span class="n">InvalidAuthToken</span><span class="p">()</span>
<span class="k">class</span> <span class="nc">CognitoAuthentication</span><span class="p">(</span>
<span class="n">CognitoAuthenticationMixin</span><span class="p">,</span> <span class="n">authentication</span><span class="o">.</span><span class="n">BaseAuthentication</span>
<span class="p">):</span>
<span class="nd">@staticmethod</span>
<span class="k">def</span> <span class="nf">get_auth_token</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">return</span> <span class="n">request</span><span class="o">.</span><span class="n">META</span><span class="p">[</span><span class="s">"HTTP_AUTHORIZATION"</span><span class="p">]</span>
<span class="k">except</span> <span class="nb">Exception</span><span class="p">:</span>
<span class="k">raise</span> <span class="n">NoAuthToken</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">authenticate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">):</span>
<span class="n">user</span> <span class="o">=</span> <span class="nb">super</span><span class="p">(</span><span class="n">CognitoAuthentication</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span><span class="o">.</span><span class="n">authenticate</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
<span class="k">return</span> <span class="n">user</span><span class="p">,</span> <span class="bp">None</span>
</code></pre></div></div>
<p>Run the server and visit http://localhost:8000 or the appropriate location to open the django backend server. Navigate to <code class="highlighter-rouge">/articles</code>. It should return a HTTP 401 if everthing is configured properly.
<img src="/assets/images/drf_cognito/http_401.png" alt="" /></p>
<h2 id="final-steps">Final steps</h2>
<p>Now, we should have two servers running, one on port 8000 (backend server) and the other on port 3000 (frontend server).</p>
<p><code class="highlighter-rouge">Note:</code> Since we’re running everything locally, we cannot invoke the trigger <code class="highlighter-rouge">Post Confirmation</code> to create a user(when the signup is done from the frontend), in our django backend. This is the better way to have an API in django to create users and have it accessible only to the lambda function which is triggering it.
<img src="/assets/images/drf_cognito/post_confirmation_trigger.png" alt="" /></p>
<p>So instead we’re going to manually grab the <code class="highlighter-rouge">idToken</code> from frontend and pass it as <code class="highlighter-rouge">Authentication</code> headers to the backend since both are connected to the same userpool and identitypool.</p>
<p>First, goto frontend & signup with a user and confirm the user with the OTP sent to email. Once done, before signing in, right click in your browser and click on <code class="highlighter-rouge">Inspect Element</code>, and go to the <code class="highlighter-rouge">Network</code> tab. Then perform signin with appropriate credentials and click on the API Calls made to <code class="highlighter-rouge">cognito-idp.{region}.amazonaws.com</code>. In the 3rd or 4th tab, you’ll find a JSON with <code class="highlighter-rouge">AuthenticationResult</code>. Expand that and copy the value of <code class="highlighter-rouge">idToken</code>.
<img src="/assets/images/drf_cognito/during_login.png" alt="" /></p>
<p>In the backend, goto django admin (http://localhost:8000/admin), signin with superuser credentials and create a new user in the <code class="highlighter-rouge">Users</code> page. Make sure to have the same username & email as mentioned which signing up in the frontend application. Once done save the user
<img src="/assets/images/drf_cognito/username_email.png" alt="" /></p>
<p>Then navigate to the articles page of backend server i.e. <code class="highlighter-rouge">http://localhost:8000/articles</code>. To manually pass headers you can either use a chrome extension called <code class="highlighter-rouge">Mod Header</code> or use <code class="highlighter-rouge">Postman</code> to make the API Call.</p>
<p>For <code class="highlighter-rouge">mod header</code>, just add Authorization in the name and paste the value of <code class="highlighter-rouge">idToken</code> copied earlier and refresh the page.
<img src="/assets/images/drf_cognito/mod_header.png" alt="" /></p>
<p>For <code class="highlighter-rouge">postman</code>, just add the value of idToken in Authentication header and submit the request.
<img src="/assets/images/drf_cognito/postman.png" alt="" /></p>yvsssantoshIn this tutorial we’ll see how to integrate AWS Cognito together with Django Rest Framework. The end result would be that most of the details related to the user will be stored & managed by AWS cognito which reduces the hassle of managing users. Setting up the frontend (with AWS Amplify + Cognito) Lets first create a simple react app. Please make sure to install latest & stable version of nodejs and npm # Create a new react app and cd into it npx create-react-app frontend_drf_cognito && cd $_ # Install AWS Amplify yarn add aws-amplify @aws-amplify/ui-react aws-amplify-react # In another terminal/prompt, globally install AWS Amplify CLI npm install -g @aws-amplify/cli Now that the initial setup is done, lets initialize AWS Amplify in our project root directory using the command amplify init. Give the details as per the image below We can also import existing amplify app using amplify pull Note: Make sure that you have authentication module enabled in amplify. If not, just add it using amplify add auth. Once done, run the command amplify push which will provision the required resources i.e. creating userpool, creating roles with required permissions, etc. Now that cognito userpool has been created, open the file src/aws-exports.js. It should be something like this ... const awsmobile = { ... ... ... "aws_user_pools_id": "ap-southeast-1_XXXXXXXXX", "aws_user_pools_web_client_id": "XXXXXXXXXXXXXXXXXXXXXXX", ... }; export default awsmobile; Make sure to take note of aws_user_pools_id and aws_user_pools_web_client_id as they’ll be used as environment variables in the django application. We’ll be setting them to the vars AWS_APP_CLIENT_ID and AWS_USER_POOLS_ID Finally start the frontend server using yarn start Setting up the backend (Django Application) Lets look at some simple steps to setup a django project with drf. Also please run cd .. to make sure that we’re not mixing frontend and backend in the same directories. Its always better to keep both of them in different directories # Setup virtualenv mkvirtualenv drf_cognito -p python3 # Selecting an existing virtual environment workon drf_cognito # Installing django, drf and python-jose pip install django djangorestframework python-jose # Initialize the project django-admin startproject drf_cognito # Create a demo app django-admin startapp articles # Better to have separate module for cognito auth django-admin startapp cognito Once we’re ready with the app, lets add a few lines to settings. Also make sure to set the environment variables AWS_APP_CLIENT_ID and AWS_USER_POOLS_ID which were generated in cognito setup above. backend_drf_cognito/settings.py ... ... ... # Application definition INSTALLED_APPS = [ ... ... "rest_framework", ] LOCAL_APPS = [ "articles", ] INSTALLED_APPS += LOCAL_APPS MIDDLEWARE = [ ... "django.contrib.auth.middleware.AuthenticationMiddleware", "cognito.middleware.CognitoAuthMiddleware", ... ] ... ... ... # AWS Cognito Configuration COGNITO_CONFIG = { # URL consists of https://{pool-name}.auth.{region}.amazoncognito.com/oauth2/token # Replace it below accordingly "url": "https://frontenddrfcognito77b3bdee_userpool_77b3bdee-dev.auth.ap-southeast-2.amazoncognito.com/oauth2/token", "app_client_id": os.environ["AWS_APP_CLIENT_ID"], "region": "ap-southeast-2", "aws_user_pools_id": os.environ["AWS_USER_POOLS_ID"], "aws_user_pools_web_client_id": os.environ["AWS_APP_CLIENT_ID"], } # Rest Framework settings REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": [ "cognito.authentication.CognitoAuthentication", ], } For simplicity lets just have name of the article in the model. articles/models.py # Default imports from django.db import models # Custom imports # None class Article(models.Model): name = models.CharField(max_length=50) def __str__(self) -> str: return self.name A serializer for articles articles/serializers.py # Default imports from rest_framework import serializers # Custom imports from .models import Article class ArticleSerializer(serializers.ModelSerializer): class Meta: model = Article fields = "__all__" Standard generics template for listing & creating articles using DRF articles/views.py # Default imports from rest_framework import generics # Custom imports from .models import Article from .serializers import ArticleSerializer class ListCreateArticles(generics.ListCreateAPIView): serializer_class = ArticleSerializer queryset = Article.objects.all() URLs to navigate the the views created articles/urls.py # Default imports from django.urls import path # Custom imports from .views import ListCreateArticles urlpatterns = [ path("", ListCreateArticles.as_view(), name="list_create_articles"), ] Update project urls as well drf_cognito_auth/urls.py from django.contrib import admin from django.urls import path, include urlpatterns = [ path("admin/", admin.site.urls), path("articles/", include("articles.urls")), ] Finally make migrations and migrate python manage.py makemigrations && python manage.py migrate Integrating DRF with Cognito Now we need to integrate AWS Cognito as an authentication backend which will be internally used by django to recognise the user who is signing in and maintain their session when accessing any APIs. So let’s setup a middleware cognito/middleware.py ... # Other imports ... class CognitoAuthMiddleware( CognitoAuthenticationMixin, middleware.AuthenticationMiddleware ): @staticmethod def get_auth_token(request): try: return request.META["HTTP_AUTHORIZATION"] except Exception: raise NoAuthToken() def process_request(self, request): if request.path.startswith(reverse("admin:index")): return None request.user = SimpleLazyObject(lambda: self.authenticate(request)) For easy code reading, we have written a separate mixin for cognito authentication cognito/authentication.py ... # Other imports from . import keys # Make sure to have the keys array in the __init__.py file # in the cognito folder # Standard Format: # keys = [{...},{...}] # In order to get the keys data, visit # https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json # Replace region and userPoolId with respective values ... def get_jwt_claims(token): # get the kid from the headers prior to verification headers = jwt.get_unverified_headers(token) kid = headers["kid"] # search for the kid in the downloaded public keys key_index = -1 for i in range(len(keys)): if kid == keys[i]["kid"]: key_index = i break if key_index == -1: print("Public key not found in jwks.json") return [] # construct the public key public_key = jwk.construct(keys[key_index]) # get the last two sections of the token, # message and signature (encoded in base64) message, encoded_signature = str(token).rsplit(".", 1) # decode the signature decoded_signature = base64url_decode(encoded_signature.encode("utf-8")) # verify the signature if not public_key.verify(message.encode("utf8"), decoded_signature): print("Signature verification failed") return [] # print("Signature successfully verified") # since we passed the verification, we can now safely # use the unverified claims claims = jwt.get_unverified_claims(token) ts = claims["exp"] os.environ["TZ"] = "Asia/Kolkata" time.tzset() print( "Current Expiry of Token : {}".format( time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(ts)) ) ) # Checking token expiry if time.time() > claims["exp"]: print("Token is expired") return [] if claims["aud"] != settings.COGNITO_CONFIG["app_client_id"]: print("Token was not issued for this audience") return [] return claims class CognitoAuthenticationMixin: @staticmethod def get_auth_token(request): raise NotImplementedError() def authenticate(self, request): token = self.get_auth_token(request) try: claims = get_jwt_claims(token) if len(claims) > 0: user = UserModel.objects.get(email=claims["email"]) return user raise NoSuchClaims() except UserModel.DoesNotExist: raise NoSuchUser() except Exception: raise InvalidAuthToken() class CognitoAuthentication( CognitoAuthenticationMixin, authentication.BaseAuthentication ): @staticmethod def get_auth_token(request): try: return request.META["HTTP_AUTHORIZATION"] except Exception: raise NoAuthToken() def authenticate(self, request): user = super(CognitoAuthentication, self).authenticate(request) return user, None Run the server and visit http://localhost:8000 or the appropriate location to open the django backend server. Navigate to /articles. It should return a HTTP 401 if everthing is configured properly. Final steps Now, we should have two servers running, one on port 8000 (backend server) and the other on port 3000 (frontend server). Note: Since we’re running everything locally, we cannot invoke the trigger Post Confirmation to create a user(when the signup is done from the frontend), in our django backend. This is the better way to have an API in django to create users and have it accessible only to the lambda function which is triggering it. So instead we’re going to manually grab the idToken from frontend and pass it as Authentication headers to the backend since both are connected to the same userpool and identitypool. First, goto frontend & signup with a user and confirm the user with the OTP sent to email. Once done, before signing in, right click in your browser and click on Inspect Element, and go to the Network tab. Then perform signin with appropriate credentials and click on the API Calls made to cognito-idp.{region}.amazonaws.com. In the 3rd or 4th tab, you’ll find a JSON with AuthenticationResult. Expand that and copy the value of idToken. In the backend, goto django admin (http://localhost:8000/admin), signin with superuser credentials and create a new user in the Users page. Make sure to have the same username & email as mentioned which signing up in the frontend application. Once done save the user Then navigate to the articles page of backend server i.e. http://localhost:8000/articles. To manually pass headers you can either use a chrome extension called Mod Header or use Postman to make the API Call. For mod header, just add Authorization in the name and paste the value of idToken copied earlier and refresh the page. For postman, just add the value of idToken in Authentication header and submit the request.Rails + GraphQL and React2020-05-28T00:00:00+00:002020-05-28T00:00:00+00:00https://agiliq.com/blog/2020/05/rails-graphql-and-react<p><code class="highlighter-rouge">Note: Kindly note that a user is expected to have basic understanding of Rails, GraphQL and ReactJS before trying this project.</code></p>
<p>You can look into my previous tutorial to <a href="https://www.agiliq.com/blog/2020/04/running-rails-server-with-graphql-and-graphiql/">get started</a> with Rails and GraphQL. This tutorial will be more focusing on building the frontend.</p>
<p>The entire code for this tutorial can be found <a href="https://github.com/yvsssantosh/posts_graphql">here</a>. I recommend starting from the <code class="highlighter-rouge">init</code> branch and go though the tutorial</p>
<h2 id="setup-the-backend">Setup the backend</h2>
<p>Before we get started with generating models, lets enable cors middleware on our project, because we need it to test the code locally.</p>
<p>Just navigate to <code class="highlighter-rouge">Gemfile</code> and uncomment <code class="highlighter-rouge">rack-cors</code></p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>...
<span class="c"># Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible</span>
gem <span class="s1">'rack-cors'</span>
<span class="c"># The line above would be commented, just uncomment it</span>
...
</code></pre></div></div>
<p>Once this is done, run <code class="highlighter-rouge">bundle install</code> in the root directory of this project so that a new file <code class="highlighter-rouge">config/initializers/cors.rb</code> will be generated. Update it with the following code below.</p>
<p><code class="highlighter-rouge">Note: This is FOR DEVELOPMENT PURPOSES ONLY</code></p>
<div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># cors.rb</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">config</span><span class="p">.</span><span class="nf">middleware</span><span class="p">.</span><span class="nf">insert_before</span> <span class="mi">0</span><span class="p">,</span> <span class="no">Rack</span><span class="o">::</span><span class="no">Cors</span> <span class="k">do</span>
<span class="n">allow</span> <span class="k">do</span>
<span class="n">origins</span> <span class="s1">'*'</span>
<span class="n">resource</span> <span class="s1">'*'</span><span class="p">,</span>
<span class="ss">headers: :any</span><span class="p">,</span>
<span class="ss">methods: </span><span class="p">[</span><span class="ss">:get</span><span class="p">,</span> <span class="ss">:post</span><span class="p">,</span> <span class="ss">:put</span><span class="p">,</span> <span class="ss">:patch</span><span class="p">,</span> <span class="ss">:delete</span><span class="p">,</span> <span class="ss">:options</span><span class="p">,</span> <span class="ss">:head</span><span class="p">]</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Lets generate the models now that we’re done with setting up the middleware which will help our frontend server to connect to backend server without blocking any insecure requests.</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Generating models</span>
<span class="c"># Generating User model with fields</span>
<span class="c"># `name` : datatype -> string</span>
<span class="c"># `email` : datatype -> string</span>
rails generate model User name:string email:string
<span class="c"># Generating Poll model with fields </span>
<span class="c"># `created_by` : datatype -> User</span>
<span class="c"># `question` : datatype -> string</span>
rails generate model Poll user:belongs_to question:string
<span class="c"># Generating Choice model with fields </span>
<span class="c"># `choice_text` : datatype -> string</span>
<span class="c"># `poll` : datatype -> Poll (FK)</span>
rails generate model Choice poll:belongs_to choice_text:string
<span class="c"># Generating Vote model with fields </span>
<span class="c"># `choice` : datatype -> Choice (FK)</span>
<span class="c"># `poll` : datatype -> Poll (FK)</span>
<span class="c"># `user` : datatype -> User (FK)</span>
rails generate model Vote choice:belongs_to poll:belongs_to user:belongs_to
<span class="c"># Make sure to add the `has_many` relationship where</span>
<span class="c"># each foreign key has been mentioned.</span>
<span class="c"># Refer `user.rb`, `poll.rb` and `choice.rb` in `app/models/` directory for the same</span>
<span class="c"># Unique Key Migration</span>
rails generate migration VotesUniqueConstraint
</code></pre></div></div>
<p>The last line above generates a migration where we’re going to specify unique together relationship for fields <code class="highlighter-rouge">user_id</code> and <code class="highlighter-rouge">poll_id</code> in <code class="highlighter-rouge">Vote</code> model. This is to make sure that a user can vote for a choice present in a poll i.e., without this, a user can vote to POLL_1 with a choice CH_1 even though CH_1 is not a choice in that poll.</p>
<div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># TIMESTAMP_votes_unique_constraint.rb</span>
<span class="k">class</span> <span class="nc">VotesUniqueConstraint</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Migration</span><span class="p">[</span><span class="mf">6.0</span><span class="p">]</span>
<span class="k">def</span> <span class="nf">change</span>
<span class="n">add_index</span> <span class="ss">:votes</span><span class="p">,</span> <span class="p">[</span><span class="ss">:user_id</span><span class="p">,</span> <span class="ss">:poll_id</span><span class="p">],</span> <span class="ss">unique: </span><span class="kp">true</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Subsequently updating the votes model (<code class="highlighter-rouge">votes.rb</code>) as well</p>
<div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Vote</span> <span class="o"><</span> <span class="no">ApplicationRecord</span>
<span class="n">belongs_to</span> <span class="ss">:choice</span>
<span class="n">belongs_to</span> <span class="ss">:poll</span>
<span class="n">belongs_to</span> <span class="ss">:user</span>
<span class="c1"># Unique together constraint</span>
<span class="n">validates</span> <span class="ss">:user_id</span><span class="p">,</span> <span class="ss">uniqueness: </span><span class="p">{</span><span class="ss">scope: :poll</span><span class="p">}</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Lets run a seed on the db to generate some data. For this tutorial, we’ll be using <code class="highlighter-rouge">Faker</code></p>
<div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># seeds.rb</span>
<span class="c1"># We're using Faker to generate random emails, for testing</span>
<span class="mi">50</span><span class="p">.</span><span class="nf">times</span> <span class="k">do</span>
<span class="no">User</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="ss">name: </span><span class="no">Faker</span><span class="o">::</span><span class="no">Name</span><span class="p">.</span><span class="nf">name</span><span class="p">,</span> <span class="ss">email: </span><span class="no">Faker</span><span class="o">::</span><span class="no">Internet</span><span class="p">.</span><span class="nf">email</span><span class="p">)</span>
<span class="k">end</span>
<span class="mi">5</span><span class="p">.</span><span class="nf">times</span> <span class="k">do</span>
<span class="n">poll</span> <span class="o">=</span> <span class="no">Poll</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="ss">question: </span><span class="no">Faker</span><span class="o">::</span><span class="no">Lorem</span><span class="p">.</span><span class="nf">question</span><span class="p">,</span> <span class="ss">user: </span><span class="no">User</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="nb">rand</span> <span class="mi">1</span><span class="o">..</span><span class="mi">50</span><span class="p">))</span>
<span class="mi">4</span><span class="p">.</span><span class="nf">times</span> <span class="k">do</span>
<span class="n">poll</span><span class="p">.</span><span class="nf">choices</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="ss">choice_text: </span><span class="no">Faker</span><span class="o">::</span><span class="no">Lorem</span><span class="p">.</span><span class="nf">sentence</span><span class="p">(</span><span class="ss">word_count: </span><span class="mi">3</span><span class="p">))</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="mi">100</span><span class="p">.</span><span class="nf">times</span> <span class="k">do</span>
<span class="n">poll</span> <span class="o">=</span> <span class="no">Poll</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="nb">rand</span> <span class="mi">1</span><span class="o">..</span><span class="mi">5</span><span class="p">)</span>
<span class="no">Vote</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="ss">user: </span><span class="no">User</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="nb">rand</span> <span class="mi">1</span><span class="o">..</span><span class="mi">50</span><span class="p">),</span> <span class="ss">poll: </span><span class="n">poll</span><span class="p">,</span> <span class="ss">choice: </span><span class="n">poll</span><span class="p">.</span><span class="nf">choices</span><span class="p">.</span><span class="nf">sample</span><span class="p">)</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Run the command to migrate and seed the db</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rails db:migrate db:seed
</code></pre></div></div>
<h3 id="installing-graphql">Installing GraphQL</h3>
<p>Now that our initial models and code is ready on rails, we need to setup GraphQL on rails</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Setting up GraphQL code in our project</span>
<span class="c"># Note: Only works if GraphQL gem is pre-installed</span>
<span class="c"># Make sure `gem 'graphql'` is in the `Gemfile`</span>
<span class="c"># Then, run `bundle install`</span>
rails generate graphql:install
</code></pre></div></div>
<p>Note that this command is very handy and auto-generates a lot of code for us. After running this command, we can see a new folder <code class="highlighter-rouge">graphql</code> has been created in the <code class="highlighter-rouge">app</code> directory. Also, <code class="highlighter-rouge">config/routes.rb</code> has also been automatically updated with the default graphql endpoint, which we’ll be using to mutate and list the data in our database.</p>
<p>Configuring rails models as GraphQL Objects</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># We need to run the command for each model. So,</span>
rails generate graphql:object user
rails generate graphql:object poll
rails generate graphql:object choice
rails generate graphql:object vote
</code></pre></div></div>
<h3 id="building-query-to-respond-with-right-data">Building Query to respond with right data</h3>
<p>If it were just a Rails application, we’d have added code to the <code class="highlighter-rouge">controller.rb</code> file. But remember that since this is a GraphQL project, we will have a single endpoint serving all over data. So lets start modifying the <code class="highlighter-rouge">_type.rb</code> files which were generated earlier.</p>
<div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># user_type.rb</span>
<span class="k">module</span> <span class="nn">Types</span>
<span class="k">class</span> <span class="nc">UserType</span> <span class="o"><</span> <span class="no">Types</span><span class="o">::</span><span class="no">BaseObject</span>
<span class="n">field</span> <span class="ss">:id</span><span class="p">,</span> <span class="no">ID</span><span class="p">,</span> <span class="ss">null: </span><span class="kp">false</span>
<span class="n">field</span> <span class="ss">:name</span><span class="p">,</span> <span class="no">String</span><span class="p">,</span> <span class="ss">null: </span><span class="kp">true</span>
<span class="n">field</span> <span class="ss">:email</span><span class="p">,</span> <span class="no">String</span><span class="p">,</span> <span class="ss">null: </span><span class="kp">true</span>
<span class="n">field</span> <span class="ss">:posts</span><span class="p">,</span> <span class="p">[</span><span class="no">Types</span><span class="o">::</span><span class="no">PollType</span><span class="p">],</span> <span class="ss">null: </span><span class="kp">true</span>
<span class="n">field</span> <span class="ss">:posts_count</span><span class="p">,</span> <span class="no">Integer</span><span class="p">,</span> <span class="ss">null: </span><span class="kp">true</span>
<span class="c1"># Typical rails querying</span>
<span class="k">def</span> <span class="nf">posts_count</span>
<span class="n">object</span><span class="p">.</span><span class="nf">posts</span><span class="p">.</span><span class="nf">size</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Similarly we’ll be defining <code class="highlighter-rouge">polls_type.rb</code>, <code class="highlighter-rouge">choice_type.rb</code> and <code class="highlighter-rouge">vote_type.rb</code> based on the models generated earlier.</p>
<p>If there are any mutations, you’ll be able to find them in <code class="highlighter-rouge">app/graphql/types/mutation_type.rb</code>. We have not defined any mutations for this tutorial. They have been explained in the previous tutorial <a href="https://www.agiliq.com/blog/2020/04/running-rails-server-with-graphql-and-graphiql/">here</a>.</p>
<p>The final backend code can be seen if we can checkout to that commit</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git checkout 6dfa3cd819ae6e37eeff3fad858e744e31f1bf5b
</code></pre></div></div>
<h2 id="setting-up-frontend">Setting up frontend</h2>
<p>Create a basic react application with some standard boilerplate. I like <code class="highlighter-rouge">yarn</code>, so I’ve used it thorughout the next steps. You can use <code class="highlighter-rouge">npm</code> as well</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Creating a basic frontend project with some sample boilerplate</span>
<span class="c"># Ref: https://create-react-app.dev/docs/getting-started/</span>
npx create-react-app frontend
<span class="c"># I like to put most of the code in components folder, having</span>
<span class="c"># react-based files ending with `.jsx` extension and format the</span>
<span class="c"># code using standard. Again, its just a matter of preference.</span>
yarn add <span class="nt">-D</span> standard babel-eslint
<span class="c"># Lets make a components directory in frontend/src and create a file </span>
<span class="c"># Polls.jsx. Also we'll be moving App.js -> src/components/App.jsx &</span>
<span class="c"># its appropriate code restructuring is to be done</span>
<span class="c"># I've made some basic css changes, to view them, run</span>
git checkout ead45a7a75dc4c64c5bd7aeab439ab25b36a71a9
<span class="c"># and see `src/App.css` and use it accordingly.</span>
</code></pre></div></div>
<h3 id="installing-dependencies">Installing dependencies</h3>
<p>In frontend, we’ll be using the following modules</p>
<ol>
<li>graphql</li>
<li>apollo-boost</li>
<li>react-polls (For showing polls and pseudo voting :P)</li>
<li>react-apollo</li>
</ol>
<p>Just install the packages using the command below</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yarn add graphql apollo-boost react-polls react-apollo <span class="nt">--save</span>
</code></pre></div></div>
<p>Lets go to our main file where code execution starts (index.js). Few changes to make,</p>
<ol>
<li>We need a link to connect our graphql server to frontend</li>
<li>Do fetch calls/install axios to do complicated fetch calls</li>
<li>Add some <code class="highlighter-rouge">sagas</code> maybe? To complicate things</li>
<li>Add a state store, like <code class="highlighter-rouge">Redux</code> to complicate things even more :P ;)</li>
</ol>
<p>Anyways, sorry for scaring you here, but we don’t need all that. We’ll just use apollo-client for this and it’ll handle everything for us. That’s a breather right!!</p>
<p>All we need to do is to link to our graphql server (running at http://localhost:5000) to ApolloProvider(as a prop) and make the <code class="highlighter-rouge">App</code> component a child for it. That’s all! More on this can be found <a href="https://www.apollographql.com/docs/react/get-started/">here</a></p>
<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Default imports</span>
<span class="c1">// Same as before</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ApolloProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="s1">'react-apollo'</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ApolloClient</span> <span class="p">}</span> <span class="k">from</span> <span class="s1">'apollo-client'</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">createHttpLink</span> <span class="p">}</span> <span class="k">from</span> <span class="s1">'apollo-link-http'</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">InMemoryCache</span> <span class="p">}</span> <span class="k">from</span> <span class="s1">'apollo-cache-inmemory'</span>
<span class="c1">// Custom Imports</span>
<span class="c1">// Same as before</span>
<span class="kd">const</span> <span class="nx">link</span> <span class="o">=</span> <span class="nx">createHttpLink</span><span class="p">({</span> <span class="na">uri</span><span class="p">:</span> <span class="s1">'http://localhost:5000/graphql'</span> <span class="p">})</span>
<span class="kd">const</span> <span class="nx">client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ApolloClient</span><span class="p">({</span> <span class="na">link</span><span class="p">:</span> <span class="nx">link</span><span class="p">,</span> <span class="na">cache</span><span class="p">:</span> <span class="k">new</span> <span class="nx">InMemoryCache</span><span class="p">()</span> <span class="p">})</span>
<span class="c1">// Making `App` as a child for ApolloProvider</span>
<span class="c1">// and passing client props to it</span>
<span class="nx">ReactDOM</span><span class="p">.</span><span class="nx">render</span><span class="p">(</span>
<span class="p"><</span><span class="nc">ApolloProvider</span> <span class="na">client=</span><span class="si">{</span><span class="nx">client</span><span class="si">}</span><span class="p">></span>
<span class="p"><</span><span class="nc">App</span> <span class="p">/></span>
<span class="p"></</span><span class="nc">ApolloProvider</span><span class="p">>,</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s1">'root'</span><span class="p">)</span>
<span class="p">)</span>
<span class="c1">// Some other code</span>
</code></pre></div></div>
<p>Now that we have Apollo connected, lets write the polls get query and populate using <code class="highlighter-rouge">react-polls</code> which we installed earlier.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
<span class="nx">polls</span> <span class="p">{</span>
<span class="nx">id</span>
<span class="nx">question</span>
<span class="nx">choices</span> <span class="p">{</span>
<span class="nx">choiceText</span>
<span class="nx">voteCount</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>We’ll be using this query to fetch the results from our backend server. Now lets update <code class="highlighter-rouge">src/components/App.jsx</code> accordingly</p>
<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Default imports</span>
<span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="s1">'react'</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">useQuery</span> <span class="p">}</span> <span class="k">from</span> <span class="s1">'react-apollo'</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">gql</span> <span class="p">}</span> <span class="k">from</span> <span class="s1">'apollo-boost'</span>
<span class="c1">// Custom imports</span>
<span class="k">import</span> <span class="s1">'../App.css'</span>
<span class="k">import</span> <span class="nx">Polls</span> <span class="k">from</span> <span class="s1">'./Polls'</span>
<span class="k">import</span> <span class="nx">reactLogo</span> <span class="k">from</span> <span class="s1">'../assets/react-logo.svg'</span>
<span class="c1">// GraphQL query to get polls, choices and the number of votes</span>
<span class="kd">const</span> <span class="nx">GET_POLLS</span> <span class="o">=</span> <span class="nx">gql</span><span class="s2">`
{
polls {
id
question
choices {
choiceText
voteCount
}
}
}
`</span>
<span class="kd">const</span> <span class="nx">App</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// Note: Anything starting with useXXX is always a hook</span>
<span class="c1">// Calling the useQuery hook of react-apollo, and observe that</span>
<span class="c1">// we have configured the graphql settings once in index,js.</span>
<span class="c1">// We don't need to configure it again in the entire app</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">loading</span><span class="p">,</span> <span class="nx">error</span><span class="p">,</span> <span class="nx">data</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">useQuery</span><span class="p">(</span><span class="nx">GET_POLLS</span><span class="p">)</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">loading</span><span class="p">)</span> <span class="k">return</span> <span class="s1">'Loading...'</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="k">return</span> <span class="s2">`Error : </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="s2">`</span>
<span class="k">return</span> <span class="p">(</span>
<span class="p"><</span><span class="nt">div</span> <span class="na">className=</span><span class="s1">'app'</span><span class="p">></span>
<span class="p"><</span><span class="nt">header</span> <span class="na">className=</span><span class="s1">'header'</span><span class="p">></span>
<span class="p"><</span><span class="nt">img</span> <span class="na">src=</span><span class="si">{</span><span class="nx">reactLogo</span><span class="si">}</span> <span class="na">className=</span><span class="s1">'logo'</span> <span class="na">alt=</span><span class="s1">'React Logo'</span> <span class="p">/></span>
<span class="p"><</span><span class="nt">h1</span> <span class="na">className=</span><span class="s1">'name'</span><span class="p">></span>Polls API<span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"></</span><span class="nt">header</span><span class="p">></span>
<span class="p"><</span><span class="nt">main</span> <span class="na">className=</span><span class="s1">'main'</span><span class="p">></span>
<span class="si">{</span><span class="cm">/*
Here we are mapping the data as react-polls has different
variable naming when compared to our Polls API
To avoid this, we can use graphql aliases, which changes
our query to the following below.
This is done intentionally to explain this concept of aliasing
as many developers think that they have to change the backend
to fit accordingly, which actually we can just use alias.
{
polls {
question: question
answers: choices {
option: choiceText
vootes: voteCount
}
}
}
*/</span><span class="si">}</span>
<span class="p"><</span><span class="nc">Polls</span> <span class="na">polls=</span><span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">polls</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span>
<span class="nx">poll</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">id</span><span class="p">:</span> <span class="nx">poll</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
<span class="na">question</span><span class="p">:</span> <span class="nx">poll</span><span class="p">.</span><span class="nx">question</span><span class="p">,</span>
<span class="na">answers</span><span class="p">:</span> <span class="nx">poll</span><span class="p">.</span><span class="nx">choices</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">choice</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">option</span><span class="p">:</span> <span class="nx">choice</span><span class="p">.</span><span class="nx">choiceText</span><span class="p">,</span>
<span class="na">votes</span><span class="p">:</span> <span class="nx">choice</span><span class="p">.</span><span class="nx">voteCount</span><span class="p">,</span>
<span class="na">id</span><span class="p">:</span> <span class="nx">choice</span><span class="p">.</span><span class="nx">id</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">)</span><span class="si">}</span>
<span class="p">/></span>
<span class="p"></</span><span class="nt">main</span><span class="p">></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p">)</span>
<span class="p">}</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">App</span>
</code></pre></div></div>
<p>Now moving on to <code class="highlighter-rouge">src/components/Polls.jsx</code>. The code has been explained in detail in the snippet below</p>
<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">useState</span><span class="p">,</span> <span class="nx">useEffect</span> <span class="p">}</span> <span class="k">from</span> <span class="s1">'react'</span>
<span class="k">import</span> <span class="nx">Poll</span> <span class="k">from</span> <span class="s1">'react-polls'</span>
<span class="kd">const</span> <span class="nx">Polls</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">polls</span> <span class="p">})</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="p">[</span><span class="nx">pollData</span><span class="p">,</span> <span class="nx">updatePollData</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">([])</span>
<span class="c1">// Some custom style for the polling box</span>
<span class="c1">// More on this can be found here => https://github.com/viniciusmeneses/react-polls#customize</span>
<span class="kd">const</span> <span class="nx">pollStyles</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">questionSeparator</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">questionSeparatorWidth</span><span class="p">:</span> <span class="s1">'question'</span><span class="p">,</span>
<span class="na">questionBold</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">questionColor</span><span class="p">:</span> <span class="s1">'#303030'</span><span class="p">,</span>
<span class="na">align</span><span class="p">:</span> <span class="s1">'center'</span><span class="p">,</span>
<span class="na">theme</span><span class="p">:</span> <span class="s1">'cyan'</span>
<span class="p">}</span>
<span class="c1">// Replacing ComponentDidMount, ComponentDidUpdate and ComponentDidUnmount</span>
<span class="c1">// And runs only when the polls are updated (the array in the end with `polls`)</span>
<span class="c1">// More on useEffect hook => https://reactjs.org/docs/hooks-effect.html</span>
<span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">updatePollData</span><span class="p">(</span><span class="nx">polls</span><span class="p">)</span>
<span class="p">},</span> <span class="p">[</span><span class="nx">polls</span><span class="p">])</span>
<span class="c1">// Simple onClick method used to handle Voting</span>
<span class="c1">// This is a pseudo method, i.e. it just updates the</span>
<span class="c1">// state of pollData and not the actual data on server</span>
<span class="kd">const</span> <span class="nx">handleVote</span> <span class="o">=</span> <span class="p">(</span><span class="nx">voteAnswer</span><span class="p">,</span> <span class="nx">pollNumber</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">newPollData</span> <span class="o">=</span> <span class="p">[...</span><span class="nx">pollData</span><span class="p">]</span>
<span class="c1">// Increment no. of votes on the choice clicked</span>
<span class="nx">newPollData</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">poll</span> <span class="o">=></span> <span class="p">(</span>
<span class="nx">poll</span><span class="p">.</span><span class="nx">id</span> <span class="o">===</span> <span class="nx">pollNumber</span> <span class="p">?</span> <span class="nx">poll</span><span class="p">.</span><span class="nx">answers</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">answer</span> <span class="o">=></span> <span class="p">(</span><span class="nx">answer</span><span class="p">.</span><span class="nx">option</span> <span class="o">===</span> <span class="nx">voteAnswer</span> <span class="p">?</span> <span class="nx">answer</span><span class="p">.</span><span class="nx">votes</span><span class="o">++</span> <span class="p">:</span> <span class="kc">null</span><span class="p">))</span> <span class="p">:</span> <span class="kc">null</span>
<span class="p">))</span>
<span class="c1">// Here we can implement a mutation to update the vote</span>
<span class="c1">// made by particular user</span>
<span class="c1">// state hook to update pollData</span>
<span class="nx">updatePollData</span><span class="p">(</span><span class="nx">newPollData</span><span class="p">)</span>
<span class="p">}</span>
<span class="c1">// Renders Poll which is from the package `react-polls`</span>
<span class="k">return</span> <span class="p">(</span>
<span class="nx">pollData</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span>
<span class="nx">poll</span> <span class="o">=></span> <span class="p">(</span>
<span class="p"><</span><span class="nt">div</span> <span class="na">key=</span><span class="si">{</span><span class="nx">poll</span><span class="p">.</span><span class="nx">id</span><span class="si">}</span><span class="p">></span>
<span class="p"><</span><span class="nt">div</span><span class="p">></span>
<span class="p"><</span><span class="nc">Poll</span>
<span class="na">question=</span><span class="si">{</span><span class="nx">poll</span><span class="p">.</span><span class="nx">question</span><span class="si">}</span>
<span class="na">answers=</span><span class="si">{</span><span class="nx">poll</span><span class="p">.</span><span class="nx">answers</span><span class="si">}</span>
<span class="na">onVote=</span><span class="si">{</span><span class="nx">voteAnswer</span> <span class="o">=></span> <span class="nx">handleVote</span><span class="p">(</span><span class="nx">voteAnswer</span><span class="p">,</span> <span class="nx">poll</span><span class="p">.</span><span class="nx">id</span><span class="p">)</span><span class="si">}</span>
<span class="na">customStyles=</span><span class="si">{</span><span class="nx">pollStyles</span><span class="si">}</span> <span class="nt">noStorage</span>
<span class="p">/></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p">)</span>
<span class="p">)</span>
<span class="p">)</span>
<span class="p">}</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">Polls</span>
</code></pre></div></div>
<p>This concludes the tutorial where we have created a basic GraphQL Application (full-stack), with</p>
<p>Backend -> Rails, Frontend -> React + Apollo</p>
<p>Please feel free to create any issues on <a href="https://github.com/yvsssantosh/polls_graphql_react/issues">my github</a></p>yvsssantoshNote: Kindly note that a user is expected to have basic understanding of Rails, GraphQL and ReactJS before trying this project. You can look into my previous tutorial to get started with Rails and GraphQL. This tutorial will be more focusing on building the frontend. The entire code for this tutorial can be found here. I recommend starting from the init branch and go though the tutorial Setup the backend Before we get started with generating models, lets enable cors middleware on our project, because we need it to test the code locally. Just navigate to Gemfile and uncomment rack-cors ... # Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible gem 'rack-cors' # The line above would be commented, just uncomment it ... Once this is done, run bundle install in the root directory of this project so that a new file config/initializers/cors.rb will be generated. Update it with the following code below. Note: This is FOR DEVELOPMENT PURPOSES ONLY # cors.rb Rails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins '*' resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head] end end Lets generate the models now that we’re done with setting up the middleware which will help our frontend server to connect to backend server without blocking any insecure requests. # Generating models # Generating User model with fields # `name` : datatype -> string # `email` : datatype -> string rails generate model User name:string email:string # Generating Poll model with fields # `created_by` : datatype -> User # `question` : datatype -> string rails generate model Poll user:belongs_to question:string # Generating Choice model with fields # `choice_text` : datatype -> string # `poll` : datatype -> Poll (FK) rails generate model Choice poll:belongs_to choice_text:string # Generating Vote model with fields # `choice` : datatype -> Choice (FK) # `poll` : datatype -> Poll (FK) # `user` : datatype -> User (FK) rails generate model Vote choice:belongs_to poll:belongs_to user:belongs_to # Make sure to add the `has_many` relationship where # each foreign key has been mentioned. # Refer `user.rb`, `poll.rb` and `choice.rb` in `app/models/` directory for the same # Unique Key Migration rails generate migration VotesUniqueConstraint The last line above generates a migration where we’re going to specify unique together relationship for fields user_id and poll_id in Vote model. This is to make sure that a user can vote for a choice present in a poll i.e., without this, a user can vote to POLL_1 with a choice CH_1 even though CH_1 is not a choice in that poll. # TIMESTAMP_votes_unique_constraint.rb class VotesUniqueConstraint < ActiveRecord::Migration[6.0] def change add_index :votes, [:user_id, :poll_id], unique: true end end Subsequently updating the votes model (votes.rb) as well class Vote < ApplicationRecord belongs_to :choice belongs_to :poll belongs_to :user # Unique together constraint validates :user_id, uniqueness: {scope: :poll} end Lets run a seed on the db to generate some data. For this tutorial, we’ll be using Faker # seeds.rb # We're using Faker to generate random emails, for testing 50.times do User.create(name: Faker::Name.name, email: Faker::Internet.email) end 5.times do poll = Poll.create(question: Faker::Lorem.question, user: User.find(rand 1..50)) 4.times do poll.choices.create(choice_text: Faker::Lorem.sentence(word_count: 3)) end end 100.times do poll = Poll.find(rand 1..5) Vote.create(user: User.find(rand 1..50), poll: poll, choice: poll.choices.sample) end Run the command to migrate and seed the db rails db:migrate db:seed Installing GraphQL Now that our initial models and code is ready on rails, we need to setup GraphQL on rails # Setting up GraphQL code in our project # Note: Only works if GraphQL gem is pre-installed # Make sure `gem 'graphql'` is in the `Gemfile` # Then, run `bundle install` rails generate graphql:install Note that this command is very handy and auto-generates a lot of code for us. After running this command, we can see a new folder graphql has been created in the app directory. Also, config/routes.rb has also been automatically updated with the default graphql endpoint, which we’ll be using to mutate and list the data in our database. Configuring rails models as GraphQL Objects # We need to run the command for each model. So, rails generate graphql:object user rails generate graphql:object poll rails generate graphql:object choice rails generate graphql:object vote Building Query to respond with right data If it were just a Rails application, we’d have added code to the controller.rb file. But remember that since this is a GraphQL project, we will have a single endpoint serving all over data. So lets start modifying the _type.rb files which were generated earlier. # user_type.rb module Types class UserType < Types::BaseObject field :id, ID, null: false field :name, String, null: true field :email, String, null: true field :posts, [Types::PollType], null: true field :posts_count, Integer, null: true # Typical rails querying def posts_count object.posts.size end end end Similarly we’ll be defining polls_type.rb, choice_type.rb and vote_type.rb based on the models generated earlier. If there are any mutations, you’ll be able to find them in app/graphql/types/mutation_type.rb. We have not defined any mutations for this tutorial. They have been explained in the previous tutorial here. The final backend code can be seen if we can checkout to that commit git checkout 6dfa3cd819ae6e37eeff3fad858e744e31f1bf5b Setting up frontend Create a basic react application with some standard boilerplate. I like yarn, so I’ve used it thorughout the next steps. You can use npm as well # Creating a basic frontend project with some sample boilerplate # Ref: https://create-react-app.dev/docs/getting-started/ npx create-react-app frontend # I like to put most of the code in components folder, having # react-based files ending with `.jsx` extension and format the # code using standard. Again, its just a matter of preference. yarn add -D standard babel-eslint # Lets make a components directory in frontend/src and create a file # Polls.jsx. Also we'll be moving App.js -> src/components/App.jsx & # its appropriate code restructuring is to be done # I've made some basic css changes, to view them, run git checkout ead45a7a75dc4c64c5bd7aeab439ab25b36a71a9 # and see `src/App.css` and use it accordingly. Installing dependencies In frontend, we’ll be using the following modules graphql apollo-boost react-polls (For showing polls and pseudo voting :P) react-apollo Just install the packages using the command below yarn add graphql apollo-boost react-polls react-apollo --save Lets go to our main file where code execution starts (index.js). Few changes to make, We need a link to connect our graphql server to frontend Do fetch calls/install axios to do complicated fetch calls Add some sagas maybe? To complicate things Add a state store, like Redux to complicate things even more :P ;) Anyways, sorry for scaring you here, but we don’t need all that. We’ll just use apollo-client for this and it’ll handle everything for us. That’s a breather right!! All we need to do is to link to our graphql server (running at http://localhost:5000) to ApolloProvider(as a prop) and make the App component a child for it. That’s all! More on this can be found here // Default imports // Same as before import { ApolloProvider } from 'react-apollo' import { ApolloClient } from 'apollo-client' import { createHttpLink } from 'apollo-link-http' import { InMemoryCache } from 'apollo-cache-inmemory' // Custom Imports // Same as before const link = createHttpLink({ uri: 'http://localhost:5000/graphql' }) const client = new ApolloClient({ link: link, cache: new InMemoryCache() }) // Making `App` as a child for ApolloProvider // and passing client props to it ReactDOM.render( <ApolloProvider client={client}> <App /> </ApolloProvider>, document.getElementById('root') ) // Some other code Now that we have Apollo connected, lets write the polls get query and populate using react-polls which we installed earlier. { polls { id question choices { choiceText voteCount } } } We’ll be using this query to fetch the results from our backend server. Now lets update src/components/App.jsx accordingly // Default imports import React from 'react' import { useQuery } from 'react-apollo' import { gql } from 'apollo-boost' // Custom imports import '../App.css' import Polls from './Polls' import reactLogo from '../assets/react-logo.svg' // GraphQL query to get polls, choices and the number of votes const GET_POLLS = gql` { polls { id question choices { choiceText voteCount } } } ` const App = () => { // Note: Anything starting with useXXX is always a hook // Calling the useQuery hook of react-apollo, and observe that // we have configured the graphql settings once in index,js. // We don't need to configure it again in the entire app const { loading, error, data } = useQuery(GET_POLLS) if (loading) return 'Loading...' if (error) return `Error : ${error.message}` return ( <div className='app'> <header className='header'> <img src={reactLogo} className='logo' alt='React Logo' /> <h1 className='name'>Polls API</h1> </header> <main className='main'> {/* Here we are mapping the data as react-polls has different variable naming when compared to our Polls API To avoid this, we can use graphql aliases, which changes our query to the following below. This is done intentionally to explain this concept of aliasing as many developers think that they have to change the backend to fit accordingly, which actually we can just use alias. { polls { question: question answers: choices { option: choiceText vootes: voteCount } } } */} <Polls polls={data.polls.map( poll => { return { id: poll.id, question: poll.question, answers: poll.choices.map(choice => { return { option: choice.choiceText, votes: choice.voteCount, id: choice.id } }) } } )} /> </main> </div> ) } export default App Now moving on to src/components/Polls.jsx. The code has been explained in detail in the snippet below import React, { useState, useEffect } from 'react' import Poll from 'react-polls' const Polls = ({ polls }) => { const [pollData, updatePollData] = useState([]) // Some custom style for the polling box // More on this can be found here => https://github.com/viniciusmeneses/react-polls#customize const pollStyles = { questionSeparator: true, questionSeparatorWidth: 'question', questionBold: true, questionColor: '#303030', align: 'center', theme: 'cyan' } // Replacing ComponentDidMount, ComponentDidUpdate and ComponentDidUnmount // And runs only when the polls are updated (the array in the end with `polls`) // More on useEffect hook => https://reactjs.org/docs/hooks-effect.html useEffect(() => { updatePollData(polls) }, [polls]) // Simple onClick method used to handle Voting // This is a pseudo method, i.e. it just updates the // state of pollData and not the actual data on server const handleVote = (voteAnswer, pollNumber) => { const newPollData = [...pollData] // Increment no. of votes on the choice clicked newPollData.map(poll => ( poll.id === pollNumber ? poll.answers.map(answer => (answer.option === voteAnswer ? answer.votes++ : null)) : null )) // Here we can implement a mutation to update the vote // made by particular user // state hook to update pollData updatePollData(newPollData) } // Renders Poll which is from the package `react-polls` return ( pollData.map( poll => ( <div key={poll.id}> <div> <Poll question={poll.question} answers={poll.answers} onVote={voteAnswer => handleVote(voteAnswer, poll.id)} customStyles={pollStyles} noStorage /> </div> </div> ) ) ) } export default Polls This concludes the tutorial where we have created a basic GraphQL Application (full-stack), with Backend -> Rails, Frontend -> React + Apollo Please feel free to create any issues on my githubBuilding a structural directive in Angular2020-05-23T00:00:00+00:002020-05-23T00:00:00+00:00https://agiliq.com/blog/2020/05/building-structural-directives-in-angular<p>In this blog post, we will create a very simple structural directive, but before that we will take a look at the builtin structural directive <strong><code class="highlighter-rouge">ngIf</code></strong> and what Angular does behine the scenes on structural directive.</p>
<h3 id="ngif">*ngIf</h3>
<p>When an Angular project is created, it creates a component named <code class="highlighter-rouge">app</code> and we are going to use this component in our example.</p>
<p>In your file <code class="highlighter-rouge">app.component.ts</code> add a property <code class="highlighter-rouge">isVisible</code> and set this to <code class="highlighter-rouge">true</code> as below</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">Component</span> <span class="p">}</span> <span class="k">from</span> <span class="s1">'@angular/core'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Component</span><span class="p">({</span>
<span class="na">selector</span><span class="p">:</span> <span class="s1">'app-root'</span><span class="p">,</span>
<span class="na">templateUrl</span><span class="p">:</span> <span class="s1">'./app.component.html'</span><span class="p">,</span>
<span class="na">styleUrls</span><span class="p">:</span> <span class="p">[</span><span class="s1">'./app.component.css'</span><span class="p">]</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">AppComponent</span> <span class="p">{</span>
<span class="nx">isVisible</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>and now in the template <code class="highlighter-rouge">app.component.html</code>, we will use <code class="highlighter-rouge">ngIf</code> to control the DOM element, add the following to <code class="highlighter-rouge">app.component.html</code></p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><p</span> <span class="err">*</span><span class="na">ngIf=</span><span class="s">"isVisible"</span><span class="nt">></span>
When life gives you lemons, sell them and buy a pineapple.
<span class="nt"></p></span>
<span class="nt"><p</span> <span class="err">*</span><span class="na">ngIf=</span><span class="s">"!isVisible"</span><span class="nt">></span>
Keep calm and eat an Apple.
<span class="nt"></p></span>
</code></pre></div></div>
<p>now run the application using the command <code class="highlighter-rouge">ng serve</code> and you should see the following text displayed</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>When life gives you lemons, sell them and buy a pineapple.
</code></pre></div></div>
<p>this is because the boolean property <code class="highlighter-rouge">isVisible</code> is set to <code class="highlighter-rouge">true</code> and Angular rendered the first paragraph.</p>
<p><em>Note: ngIf doesn’t hide elements, it adds/removes the element from DOM on which it is placed.</em></p>
<h3 id="structural-directives-behind-the-scenes">Structural directives behind the scenes</h3>
<p>Did you observe the use of asterix <code class="highlighter-rouge">*</code> in <code class="highlighter-rouge">*ngIf</code>? it is just a syntactic sugar, behind the scenes Angular translates <code class="highlighter-rouge">*ngIf</code> in to <code class="highlighter-rouge"><ng-template></code> around which the host element (<code class="highlighter-rouge"><p></code> in our example) is wrapped.</p>
<p>So this</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><p</span> <span class="err">*</span><span class="na">ngIf=</span><span class="s">"isVisible"</span><span class="nt">></span>
When life gives you lemons, sell them and buy a pineapple.
<span class="nt"></p></span>
</code></pre></div></div>
<p>is translated to</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><ng-template</span> <span class="err">[</span><span class="na">ngIf</span><span class="err">]="</span><span class="na">isVisible</span><span class="err">"</span><span class="nt">></span>
<span class="nt"><p></span>When life gives you lemons, sell them and buy a pineapple.<span class="nt"></p></span>
<span class="nt"></ng-template></span>
</code></pre></div></div>
<h3 id="creating-structural-directive">Creating structural directive</h3>
<p>Now that we know how <code class="highlighter-rouge">ngIf</code> works, we will create a custom structural directive which will be opposite to <code class="highlighter-rouge">ngIf</code>. That means, this directive will remove the element from the DOM if the conditiion is true.</p>
<p>create a file named <code class="highlighter-rouge">customcondition.directive.ts</code> and add the following code</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">Directive</span> <span class="p">}</span> <span class="k">from</span> <span class="s1">'@angular/core'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Directive</span><span class="p">({</span>
<span class="na">selector</span><span class="p">:</span> <span class="s1">'[appCustomCondition]'</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">CustomCondition</span> <span class="p">{</span>
<span class="p">}</span>
</code></pre></div></div>
<p>after this, to get access to the <code class="highlighter-rouge"><ng-template></code> and to also get access to the place in the DOM where we want to render it, we need to inject couple of things namely <code class="highlighter-rouge">TemplateRef</code> and <code class="highlighter-rouge">ViewContainerRef</code> in directive constructor like this</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">Directive</span><span class="p">,</span> <span class="nx">TemplateRef</span><span class="p">,</span> <span class="nx">ViewContainerRef</span> <span class="p">}</span> <span class="k">from</span> <span class="s1">'@angular/core'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Directive</span><span class="p">({</span>
<span class="na">selector</span><span class="p">:</span> <span class="s1">'[appCustomCondition]'</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">CustomCondition</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span><span class="kr">private</span> <span class="nx">tRef</span><span class="p">:</span> <span class="nx">TemplateRef</span><span class="o"><</span><span class="nx">any</span><span class="o">></span><span class="p">,</span> <span class="kr">private</span> <span class="nx">vcRef</span><span class="p">:</span> <span class="nx">ViewContainerRef</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>and lastly, since we are going to bind true/false value to our custom directive, we need someway to relay this value from <code class="highlighter-rouge">app</code> component to our custom <code class="highlighter-rouge">directive</code>.
For this, we need to decorate the property with <code class="highlighter-rouge">@Input</code> decorator and then we will use the dependencies injected in our constructor to add/remove the element from DOM.</p>
<p>so the final code in <code class="highlighter-rouge">customcondition.directive.ts</code> will look like this</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">Directive</span><span class="p">,</span> <span class="nx">Input</span><span class="p">,</span> <span class="nx">TemplateRef</span><span class="p">,</span> <span class="nx">ViewContainerRef</span> <span class="p">}</span> <span class="k">from</span> <span class="s1">'@angular/core'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Directive</span><span class="p">({</span>
<span class="na">selector</span><span class="p">:</span> <span class="s1">'[appCustomCondition]'</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">CustomCondition</span> <span class="p">{</span>
<span class="p">@</span><span class="nd">Input</span><span class="p">()</span> <span class="kd">set</span> <span class="nx">appCustomCondition</span><span class="p">(</span><span class="nx">value</span><span class="p">:</span> <span class="kr">boolean</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">value</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">vcRef</span><span class="p">.</span><span class="nx">createEmbeddedView</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">tRef</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">vcRef</span><span class="p">.</span><span class="nx">clear</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">constructor</span><span class="p">(</span><span class="kr">private</span> <span class="na">tRef</span><span class="p">:</span> <span class="nx">TemplateRef</span><span class="o"><</span><span class="nx">any</span><span class="o">></span><span class="p">,</span> <span class="kr">private</span> <span class="na">vcRef</span><span class="p">:</span> <span class="nx">ViewContainerRef</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p><em>Note: The name of the directive selector and the property decorated with @Input should be the same.</em></p>
<p>and our template <code class="highlighter-rouge">app.component.html</code> will look like this</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><p</span> <span class="err">*</span><span class="na">appCustomCondition=</span><span class="s">"isVisible"</span><span class="nt">></span>
When life gives you lemons, sell them and buy a pineapple
<span class="nt"></p></span>
<span class="nt"><p</span> <span class="err">*</span><span class="na">appCustomCondition=</span><span class="s">"!isVisible"</span><span class="nt">></span>
Keep calm and eat an Apple.
<span class="nt"></p></span>
</code></pre></div></div>
<p>now when you run this using <code class="highlighter-rouge">ng serve</code>, you should the output as below</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Keep calm and eat an Apple.
</code></pre></div></div>
<p>this is because our custom structural directive <code class="highlighter-rouge">appCustomCondition</code> does just the opposite of <code class="highlighter-rouge">ngIf</code>.</p>Manjunath HugarIn this blog post, we will create a very simple structural directive, but before that we will take a look at the builtin structural directive ngIf and what Angular does behine the scenes on structural directive. *ngIf When an Angular project is created, it creates a component named app and we are going to use this component in our example. In your file app.component.ts add a property isVisible and set this to true as below import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { isVisible = true; } and now in the template app.component.html, we will use ngIf to control the DOM element, add the following to app.component.html <p *ngIf="isVisible"> When life gives you lemons, sell them and buy a pineapple. </p> <p *ngIf="!isVisible"> Keep calm and eat an Apple. </p> now run the application using the command ng serve and you should see the following text displayed When life gives you lemons, sell them and buy a pineapple. this is because the boolean property isVisible is set to true and Angular rendered the first paragraph. Note: ngIf doesn’t hide elements, it adds/removes the element from DOM on which it is placed. Structural directives behind the scenes Did you observe the use of asterix * in *ngIf? it is just a syntactic sugar, behind the scenes Angular translates *ngIf in to <ng-template> around which the host element (<p> in our example) is wrapped. So this <p *ngIf="isVisible"> When life gives you lemons, sell them and buy a pineapple. </p> is translated to <ng-template [ngIf]="isVisible"> <p>When life gives you lemons, sell them and buy a pineapple.</p> </ng-template> Creating structural directive Now that we know how ngIf works, we will create a custom structural directive which will be opposite to ngIf. That means, this directive will remove the element from the DOM if the conditiion is true. create a file named customcondition.directive.ts and add the following code import { Directive } from '@angular/core'; @Directive({ selector: '[appCustomCondition]' }) export class CustomCondition { } after this, to get access to the <ng-template> and to also get access to the place in the DOM where we want to render it, we need to inject couple of things namely TemplateRef and ViewContainerRef in directive constructor like this import { Directive, TemplateRef, ViewContainerRef } from '@angular/core'; @Directive({ selector: '[appCustomCondition]' }) export class CustomCondition { constructor(private tRef: TemplateRef<any>, private vcRef: ViewContainerRef) { } } and lastly, since we are going to bind true/false value to our custom directive, we need someway to relay this value from app component to our custom directive. For this, we need to decorate the property with @Input decorator and then we will use the dependencies injected in our constructor to add/remove the element from DOM. so the final code in customcondition.directive.ts will look like this import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core'; @Directive({ selector: '[appCustomCondition]' }) export class CustomCondition { @Input() set appCustomCondition(value: boolean) { if (!value) { this.vcRef.createEmbeddedView(this.tRef); } else { this.vcRef.clear(); } } constructor(private tRef: TemplateRef<any>, private vcRef: ViewContainerRef) { } } Note: The name of the directive selector and the property decorated with @Input should be the same. and our template app.component.html will look like this <p *appCustomCondition="isVisible"> When life gives you lemons, sell them and buy a pineapple </p> <p *appCustomCondition="!isVisible"> Keep calm and eat an Apple. </p> now when you run this using ng serve, you should the output as below Keep calm and eat an Apple. this is because our custom structural directive appCustomCondition does just the opposite of ngIf.Custom attribute directives in Angular2020-05-18T00:00:00+00:002020-05-18T00:00:00+00:00https://agiliq.com/blog/2020/05/custom-attribute-directives-in-angular<p>Angular has three kinds of directives</p>
<ul>
<li><code class="highlighter-rouge">Components</code> - is a directive with a template.</li>
<li><code class="highlighter-rouge">Structural directives</code> - controls the DOM elements, using which we can add or remove elements from DOM. For ex: *ngIf, *ngFor etc.</li>
<li><code class="highlighter-rouge">Attribute directives</code> - to change the behavior or apperance of an element, component or the directive. For ex: ngClass, ngStyle etc.</li>
</ul>
<p>In this blog post, we are going to create a simple custom attribute directive, which is when placed on an element, it will change its appearance.</p>
<p>Creating a directive is very simple, you can either create it manually or using the CLI command as below</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ng generate directive highlight
</code></pre></div></div>
<p>the above command will generate a file named <code class="highlighter-rouge">highlight.directive.ts</code> and a test file <code class="highlighter-rouge">src/app/highlight.directive.spec.ts</code> inside <code class="highlighter-rouge">src/app</code> folder and it will also register your directive in <code class="highlighter-rouge">app.module.ts</code>.</p>
<p>so your <code class="highlighter-rouge">hightlight.directive.ts</code> should contain the following code</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">Directive</span> <span class="p">}</span> <span class="k">from</span> <span class="s1">'@angular/core'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Directive</span><span class="p">({</span>
<span class="na">selector</span><span class="p">:</span> <span class="s1">'[appHighlight]'</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">HighlightDirective</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>creating a directive is similar to creating a component, the only difference is that in directives we decorate our class using <code class="highlighter-rouge">@Directive</code> imported from <code class="highlighter-rouge">@angular/core</code>.
@Directive decorator has a selector which is enclosed in <code class="highlighter-rouge">[]</code> because we want to use this custom directive as an attribute in our HTML element.</p>
<p>Ok, now lets add this directive to our HTML tag, add this line to your <code class="highlighter-rouge">app.component.html</code></p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><p></span>This is <span class="nt"><span</span> <span class="na">appHighlight</span><span class="nt">></span>Angular9<span class="nt"></span></p></span>
</code></pre></div></div>
<p>now in order to make an element on which we used our custom directive available to our directive class, we need to inject a reference to that element in directive’s constructor.</p>
<p>Modify <code class="highlighter-rouge">hightlight.directive.ts</code> to look like this -</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">Directive</span><span class="p">,</span> <span class="nx">ElementRef</span> <span class="p">}</span> <span class="k">from</span> <span class="s1">'@angular/core'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Directive</span><span class="p">({</span>
<span class="na">selector</span><span class="p">:</span> <span class="s1">'[appHighlight]'</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">HighlightDirective</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span><span class="nx">el</span><span class="p">:</span> <span class="nx">ElementRef</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">el</span><span class="p">.</span><span class="nx">nativeElement</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">fontSize</span> <span class="o">=</span> <span class="s2">"20px"</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p><code class="highlighter-rouge">ElementRef</code> lets us access DOM elements directly through its <code class="highlighter-rouge">nativeElement property.</code></p>
<p>now run <code class="highlighter-rouge">ng serve</code> and you should see that the text <code class="highlighter-rouge">Angular9</code> is set to font size 20px.</p>
<p>Now this is all good, but how about changing the element style based on user driven events like say for example - onmouseover, onmouseout etc?
Yes it is possible to listen to an event and change the appearance of DOM elements using the <code class="highlighter-rouge">@HostListener</code></p>
<p>Import <code class="highlighter-rouge">HostListener</code> from <code class="highlighter-rouge">@angular/core</code></p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">Directive</span><span class="p">,</span> <span class="nx">ElementRef</span><span class="p">,</span> <span class="nx">HostListener</span> <span class="p">}</span> <span class="k">from</span> <span class="s1">'@angular/core'</span><span class="p">;</span>
</code></pre></div></div>
<p>then add the event handlers to responsd to the events - <code class="highlighter-rouge">mouseenter</code>, <code class="highlighter-rouge">mouseleave</code> as below</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">Directive</span><span class="p">,</span> <span class="nx">ElementRef</span><span class="p">,</span> <span class="nx">HostListener</span> <span class="p">}</span> <span class="k">from</span> <span class="s1">'@angular/core'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Directive</span><span class="p">({</span>
<span class="na">selector</span><span class="p">:</span> <span class="s1">'[appHighlight]'</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">HighlightDirective</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span><span class="kr">private</span> <span class="nx">el</span><span class="p">:</span> <span class="nx">ElementRef</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
<span class="p">@</span><span class="nd">HostListener</span><span class="p">(</span><span class="s1">'mouseenter'</span><span class="p">)</span> <span class="nx">onMouseEnter</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">highlight</span><span class="p">(</span><span class="s2">"20px"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">@</span><span class="nd">HostListener</span><span class="p">(</span><span class="s1">'mouseleave'</span><span class="p">)</span> <span class="nx">onMouseLeave</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">highlight</span><span class="p">(</span><span class="s2">"16px"</span><span class="p">);</span>
<span class="p">}</span>
<span class="kr">private</span> <span class="nx">highlight</span><span class="p">(</span><span class="nx">fontSize</span><span class="p">:</span> <span class="nx">string</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">el</span><span class="p">.</span><span class="nx">nativeElement</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">fontSize</span> <span class="o">=</span> <span class="nx">fontSize</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p><code class="highlighter-rouge">@HostListener</code> subscribes to the events of the DOM element on which we have placed our attribute directive, in our case we have applied custom directive to the <code class="highlighter-rouge">span</code> element.</p>
<p>Now run the app using <code class="highlighter-rouge">ng serve</code> and you should see that the font size of the text <code class="highlighter-rouge">Angular9</code> changes on mouse over and mouse leave.</p>
<p>It is also possible to send data to the directive, instead of defining the values inside the directive. We can achive this by using the <code class="highlighter-rouge">@Input</code> property, you can refer this <a href="https://www.agiliq.com/blog/2020/05/input-and-output-properties-in-angular/">post</a> for details about the <code class="highlighter-rouge">@Input</code> and <code class="highlighter-rouge">@Output</code> properties.</p>
<p>Update the file <code class="highlighter-rouge">highlight.directive.ts</code> with the following code</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">Directive</span><span class="p">,</span> <span class="nx">ElementRef</span><span class="p">,</span> <span class="nx">HostListener</span><span class="p">,</span> <span class="nx">Input</span> <span class="p">}</span> <span class="k">from</span> <span class="s1">'@angular/core'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Directive</span><span class="p">({</span>
<span class="na">selector</span><span class="p">:</span> <span class="s1">'[appHighlight]'</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">HighlightDirective</span> <span class="p">{</span>
<span class="p">@</span><span class="nd">Input</span><span class="p">()</span> <span class="nx">highlightText</span><span class="p">:</span> <span class="nx">string</span><span class="p">;</span>
<span class="kd">constructor</span><span class="p">(</span><span class="kr">private</span> <span class="nx">el</span><span class="p">:</span> <span class="nx">ElementRef</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
<span class="p">@</span><span class="nd">HostListener</span><span class="p">(</span><span class="s1">'mouseenter'</span><span class="p">)</span> <span class="nx">onMouseEnter</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">highlight</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">highlightText</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">@</span><span class="nd">HostListener</span><span class="p">(</span><span class="s1">'mouseleave'</span><span class="p">)</span> <span class="nx">onMouseLeave</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">highlight</span><span class="p">(</span><span class="s2">"16px"</span><span class="p">);</span>
<span class="p">}</span>
<span class="kr">private</span> <span class="nx">highlight</span><span class="p">(</span><span class="nx">fontSize</span><span class="p">:</span> <span class="nx">string</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">el</span><span class="p">.</span><span class="nx">nativeElement</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">fontSize</span> <span class="o">=</span> <span class="nx">fontSize</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>and our <code class="highlighter-rouge">app.component.html</code> will look like this</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><p></span>This is <span class="nt"><span</span> <span class="na">appHighlight</span> <span class="err">[</span><span class="na">highlightText</span><span class="err">]="'</span><span class="na">30px</span><span class="err">'"</span><span class="nt">></span>Angular9<span class="nt"></span></p></span>
</code></pre></div></div>
<p>notice the use singlequotes in <code class="highlighter-rouge">[highlightText]="'30px'"</code>, this can be replaced with <code class="highlighter-rouge">highlightText="30px"</code> if you want to avoid using single quotes.</p>
<p>Everything looks great but wouldn’t it be nice if there is a way to apply the directive and apply the font in the same attribute instead of having two attributes? we can do that using <code class="highlighter-rouge">@Input</code> alias.</p>
<p>replace <code class="highlighter-rouge">app.component.html</code> with the following code -</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><p></span>This is <span class="nt"><span</span> <span class="err">[</span><span class="na">appHighlight</span><span class="err">]="'</span><span class="na">30px</span><span class="err">'"</span><span class="nt">></span>Angular9<span class="nt"></span></p></span>
</code></pre></div></div>
<p>and use @Input alias in <code class="highlighter-rouge">highlight.directive.ts</code> as</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">@</span><span class="nd">Input</span><span class="p">(</span><span class="s1">'appHighlight'</span><span class="p">)</span> <span class="nx">highlightText</span><span class="p">:</span> <span class="nx">string</span><span class="p">;</span>
</code></pre></div></div>
<p>so our final code for <code class="highlighter-rouge">highlight.directive.ts</code> will look like this</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">Directive</span><span class="p">,</span> <span class="nx">ElementRef</span><span class="p">,</span> <span class="nx">HostListener</span><span class="p">,</span> <span class="nx">Input</span> <span class="p">}</span> <span class="k">from</span> <span class="s1">'@angular/core'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Directive</span><span class="p">({</span>
<span class="na">selector</span><span class="p">:</span> <span class="s1">'[appHighlight]'</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">HighlightDirective</span> <span class="p">{</span>
<span class="p">@</span><span class="nd">Input</span><span class="p">(</span><span class="s1">'appHighlight'</span><span class="p">)</span> <span class="nx">highlightText</span><span class="p">:</span> <span class="nx">string</span><span class="p">;</span>
<span class="kd">constructor</span><span class="p">(</span><span class="kr">private</span> <span class="nx">el</span><span class="p">:</span> <span class="nx">ElementRef</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
<span class="p">@</span><span class="nd">HostListener</span><span class="p">(</span><span class="s1">'mouseenter'</span><span class="p">)</span> <span class="nx">onMouseEnter</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">highlight</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">highlightText</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">@</span><span class="nd">HostListener</span><span class="p">(</span><span class="s1">'mouseleave'</span><span class="p">)</span> <span class="nx">onMouseLeave</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">highlight</span><span class="p">(</span><span class="s2">"16px"</span><span class="p">);</span>
<span class="p">}</span>
<span class="kr">private</span> <span class="nx">highlight</span><span class="p">(</span><span class="nx">fontSize</span><span class="p">:</span> <span class="nx">string</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">el</span><span class="p">.</span><span class="nx">nativeElement</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">fontSize</span> <span class="o">=</span> <span class="nx">fontSize</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>and our <code class="highlighter-rouge">app.component.html</code> will look like this</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><p></span>This is <span class="nt"><span</span> <span class="err">[</span><span class="na">appHighlight</span><span class="err">]="'</span><span class="na">30px</span><span class="err">'"</span><span class="nt">></span>Angular9<span class="nt"></span></p></span>
</code></pre></div></div>Manjunath HugarAngular has three kinds of directives Components - is a directive with a template. Structural directives - controls the DOM elements, using which we can add or remove elements from DOM. For ex: *ngIf, *ngFor etc. Attribute directives - to change the behavior or apperance of an element, component or the directive. For ex: ngClass, ngStyle etc. In this blog post, we are going to create a simple custom attribute directive, which is when placed on an element, it will change its appearance. Creating a directive is very simple, you can either create it manually or using the CLI command as below ng generate directive highlight the above command will generate a file named highlight.directive.ts and a test file src/app/highlight.directive.spec.ts inside src/app folder and it will also register your directive in app.module.ts. so your hightlight.directive.ts should contain the following code import { Directive } from '@angular/core'; @Directive({ selector: '[appHighlight]' }) export class HighlightDirective { constructor() { } } creating a directive is similar to creating a component, the only difference is that in directives we decorate our class using @Directive imported from @angular/core. @Directive decorator has a selector which is enclosed in [] because we want to use this custom directive as an attribute in our HTML element. Ok, now lets add this directive to our HTML tag, add this line to your app.component.html <p>This is <span appHighlight>Angular9</span></p> now in order to make an element on which we used our custom directive available to our directive class, we need to inject a reference to that element in directive’s constructor. Modify hightlight.directive.ts to look like this - import { Directive, ElementRef } from '@angular/core'; @Directive({ selector: '[appHighlight]' }) export class HighlightDirective { constructor(el: ElementRef) { el.nativeElement.style.fontSize = "20px"; } } ElementRef lets us access DOM elements directly through its nativeElement property. now run ng serve and you should see that the text Angular9 is set to font size 20px. Now this is all good, but how about changing the element style based on user driven events like say for example - onmouseover, onmouseout etc? Yes it is possible to listen to an event and change the appearance of DOM elements using the @HostListener Import HostListener from @angular/core import { Directive, ElementRef, HostListener } from '@angular/core'; then add the event handlers to responsd to the events - mouseenter, mouseleave as below import { Directive, ElementRef, HostListener } from '@angular/core'; @Directive({ selector: '[appHighlight]' }) export class HighlightDirective { constructor(private el: ElementRef) { } @HostListener('mouseenter') onMouseEnter() { this.highlight("20px"); } @HostListener('mouseleave') onMouseLeave() { this.highlight("16px"); } private highlight(fontSize: string) { this.el.nativeElement.style.fontSize = fontSize; } } @HostListener subscribes to the events of the DOM element on which we have placed our attribute directive, in our case we have applied custom directive to the span element. Now run the app using ng serve and you should see that the font size of the text Angular9 changes on mouse over and mouse leave. It is also possible to send data to the directive, instead of defining the values inside the directive. We can achive this by using the @Input property, you can refer this post for details about the @Input and @Output properties. Update the file highlight.directive.ts with the following code import { Directive, ElementRef, HostListener, Input } from '@angular/core'; @Directive({ selector: '[appHighlight]' }) export class HighlightDirective { @Input() highlightText: string; constructor(private el: ElementRef) { } @HostListener('mouseenter') onMouseEnter() { this.highlight(this.highlightText); } @HostListener('mouseleave') onMouseLeave() { this.highlight("16px"); } private highlight(fontSize: string) { this.el.nativeElement.style.fontSize = fontSize; } } and our app.component.html will look like this <p>This is <span appHighlight [highlightText]="'30px'">Angular9</span></p> notice the use singlequotes in [highlightText]="'30px'", this can be replaced with highlightText="30px" if you want to avoid using single quotes. Everything looks great but wouldn’t it be nice if there is a way to apply the directive and apply the font in the same attribute instead of having two attributes? we can do that using @Input alias. replace app.component.html with the following code - <p>This is <span [appHighlight]="'30px'">Angular9</span></p> and use @Input alias in highlight.directive.ts as @Input('appHighlight') highlightText: string; so our final code for highlight.directive.ts will look like this import { Directive, ElementRef, HostListener, Input } from '@angular/core'; @Directive({ selector: '[appHighlight]' }) export class HighlightDirective { @Input('appHighlight') highlightText: string; constructor(private el: ElementRef) { } @HostListener('mouseenter') onMouseEnter() { this.highlight(this.highlightText); } @HostListener('mouseleave') onMouseLeave() { this.highlight("16px"); } private highlight(fontSize: string) { this.el.nativeElement.style.fontSize = fontSize; } } and our app.component.html will look like this <p>This is <span [appHighlight]="'30px'">Angular9</span></p>Javascript pass by value and pass by reference2020-05-18T00:00:00+00:002020-05-18T00:00:00+00:00https://agiliq.com/blog/2020/05/js-pass-by-reference-vs-pass-by-value<p>In this blog post, we are going to understand pass by value and pass by reference in Javascript.</p>
<p>It is important to understand this, in order to improve the javascript development and debugging skills.</p>
<h3 id="pass-by-value">Pass by value</h3>
<p>In javascript, all primitive types are passed by value, to understand this let’s take a look at the following example -</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">myNumber</span> <span class="o">=</span> <span class="mi">10</span><span class="p">;</span>
</code></pre></div></div>
<p>here, the value <code class="highlighter-rouge">10</code> is stored somewhere in the memory and the variable <code class="highlighter-rouge">myNumber</code> has the address location of the memory where the value <code class="highlighter-rouge">10</code> is stored.</p>
<p>and now lets suppose if we setup a new variable <code class="highlighter-rouge">otherNumber</code> which is equal to <code class="highlighter-rouge">myNumber</code></p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">otherNumber</span> <span class="o">=</span> <span class="nx">myNumber</span>
</code></pre></div></div>
<p>since <code class="highlighter-rouge">myNumber</code> points to a memory location containing primitive type value <code class="highlighter-rouge">10</code>, <code class="highlighter-rouge">otherNumber</code> points to a new memory location and the copy of primitive value is stored in that memory location. That means the variables <code class="highlighter-rouge">myNumber</code> and <code class="highlighter-rouge">otherNumber</code> are pointing to two different memory locations but the same value <code class="highlighter-rouge">10</code> is stored in those two memory locations.</p>
<p>This process is called <code class="highlighter-rouge">Pass by value</code>.</p>
<p>That means, if we change <code class="highlighter-rouge">myNumber</code> then it should not affect <code class="highlighter-rouge">otherNumber</code> because they are pointing to two different locations in memory.</p>
<p>Let’s try changing the value of <code class="highlighter-rouge">myNumber</code> to 20</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">myNumber</span> <span class="o">=</span> <span class="mi">20</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">myNumber</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="nx">otherNumber</span><span class="p">)</span>
</code></pre></div></div>
<p>when you run this, you should see the following output as expected -</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>20
10
</code></pre></div></div>
<p>same approach is followed when a primitive type value is passed to the function as a parameter.</p>
<h3 id="pass-by-reference">Pass by reference</h3>
<p>All objects in Javascript
(including functions, remember functions are first class objects) are passed by reference.
consider an example</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">person</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"name"</span><span class="p">:</span> <span class="s2">"Sachin"</span><span class="p">};</span>
</code></pre></div></div>
<p>as usual, JS allocates memory to store an object <code class="highlighter-rouge">{"name": "Sachin"}</code> and the variable <code class="highlighter-rouge">person</code> points to that memory location.
but what happens when we assign <code class="highlighter-rouge">person</code> to new variable let’s say <code class="highlighter-rouge">person1</code> is interesting</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">person1</span> <span class="o">=</span> <span class="nx">person</span><span class="p">;</span>
</code></pre></div></div>
<p>now in this case, instead of creating a new memory space for <code class="highlighter-rouge">person1</code> and copying the object in to that memory, JS simply makes <code class="highlighter-rouge">person1</code> point to the same memory location where the var <code class="highlighter-rouge">person</code> is pointing to.</p>
<p>that means if we mutate (to change something) <code class="highlighter-rouge">person</code> like below</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">person</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="s2">"Vijay"</span><span class="p">;</span>
</code></pre></div></div>
<p>and log both <code class="highlighter-rouge">person</code> and <code class="highlighter-rouge">person1</code>, what do you think will happen?</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">person</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="nx">person1</span><span class="p">);</span>
</code></pre></div></div>
<p>remember that when it comes to objects, the variables which are set equal to each other simply points to the same memory location. So in this case changing <code class="highlighter-rouge">person</code> will change <code class="highlighter-rouge">person1</code> as well.
Think of it like a person having an alias name but his address remains the same.
We should see the following output</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="nl">name</span><span class="p">:</span> <span class="s2">"Vijay"</span><span class="p">}</span>
<span class="p">{</span><span class="nl">name</span><span class="p">:</span> <span class="s2">"Vijay"</span><span class="p">}</span>
</code></pre></div></div>
<p>and again, you should see the same behavior when objects are passed to the functions as parameters.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">changePerson</span><span class="p">(</span><span class="nx">pObj</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">pObj</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="s2">"Ramesh"</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>when we pass the object <code class="highlighter-rouge">person</code> to the function, it is passed by reference, let’s log <code class="highlighter-rouge">person</code> and <code class="highlighter-rouge">person1</code> now and we should see that the property <code class="highlighter-rouge">name</code> of object <code class="highlighter-rouge">person</code> and <code class="highlighter-rouge">person1</code> set to the value <code class="highlighter-rouge">Ramesh</code>.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">changePerson</span><span class="p">(</span><span class="nx">person</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="nx">person</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="nx">person1</span><span class="p">);</span>
</code></pre></div></div>
<p>This is known as <code class="highlighter-rouge">pass by reference</code>.</p>Manjunath HugarIn this blog post, we are going to understand pass by value and pass by reference in Javascript. It is important to understand this, in order to improve the javascript development and debugging skills. Pass by value In javascript, all primitive types are passed by value, to understand this let’s take a look at the following example - myNumber = 10; here, the value 10 is stored somewhere in the memory and the variable myNumber has the address location of the memory where the value 10 is stored. and now lets suppose if we setup a new variable otherNumber which is equal to myNumber otherNumber = myNumber since myNumber points to a memory location containing primitive type value 10, otherNumber points to a new memory location and the copy of primitive value is stored in that memory location. That means the variables myNumber and otherNumber are pointing to two different memory locations but the same value 10 is stored in those two memory locations. This process is called Pass by value. That means, if we change myNumber then it should not affect otherNumber because they are pointing to two different locations in memory. Let’s try changing the value of myNumber to 20 myNumber = 20 console.log(myNumber) console.log(otherNumber) when you run this, you should see the following output as expected - 20 10 same approach is followed when a primitive type value is passed to the function as a parameter. Pass by reference All objects in Javascript (including functions, remember functions are first class objects) are passed by reference. consider an example var person = {"name": "Sachin"}; as usual, JS allocates memory to store an object {"name": "Sachin"} and the variable person points to that memory location. but what happens when we assign person to new variable let’s say person1 is interesting var person1 = person; now in this case, instead of creating a new memory space for person1 and copying the object in to that memory, JS simply makes person1 point to the same memory location where the var person is pointing to. that means if we mutate (to change something) person like below person.name = "Vijay"; and log both person and person1, what do you think will happen? console.log(person); console.log(person1); remember that when it comes to objects, the variables which are set equal to each other simply points to the same memory location. So in this case changing person will change person1 as well. Think of it like a person having an alias name but his address remains the same. We should see the following output {name: "Vijay"} {name: "Vijay"} and again, you should see the same behavior when objects are passed to the functions as parameters. function changePerson(pObj) { pObj.name = "Ramesh"; } when we pass the object person to the function, it is passed by reference, let’s log person and person1 now and we should see that the property name of object person and person1 set to the value Ramesh. changePerson(person); console.log(person); console.log(person1); This is known as pass by reference.Getting started with FastAPI by re building the Django Polls Tutorial2020-05-14T00:00:00+00:002020-05-14T00:00:00+00:00https://agiliq.com/blog/2020/05/polls-api-using-fastapi<p>In this blog post we are going to rebuild Django Polls tutorial API using FastAPI.</p>
<h3 id="what-is-fastapi">What is FastAPI?</h3>
<p>FastAPI is a web framework for building APIs. As per its official page, `FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints.</p>
<p>It is easy to learn, fast and said to be high performance, on par with <code class="highlighter-rouge">NodeJS</code> and <code class="highlighter-rouge">Go</code>.`</p>
<h3 id="installation">Installation</h3>
<p>Open your terminal and run</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip install fastapi
</code></pre></div></div>
<p>also need to install ASGI server</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip install uvicorn
</code></pre></div></div>
<p>thats all, now lets quicky create some endpoints, create a file <code class="highlighter-rouge">main.py</code> and add the following</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">fastapi</span> <span class="kn">import</span> <span class="n">FastAPI</span>
<span class="n">app</span> <span class="o">=</span> <span class="n">FastAPI</span><span class="p">()</span>
<span class="nd">@app.get</span><span class="p">(</span><span class="s">"/"</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">index</span><span class="p">():</span>
<span class="k">return</span> <span class="p">{</span><span class="s">"message"</span><span class="p">:</span> <span class="s">"Welcome to the world of FastAPI!"</span><span class="p">}</span>
<span class="nd">@app.get</span><span class="p">(</span><span class="s">"/items/{item}"</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">read_item</span><span class="p">(</span><span class="n">item</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">q</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="bp">None</span><span class="p">):</span>
<span class="k">return</span> <span class="p">{</span><span class="s">"item"</span><span class="p">:</span> <span class="n">item</span><span class="p">,</span> <span class="s">"q"</span><span class="p">:</span> <span class="n">q</span><span class="p">}</span>
</code></pre></div></div>
<p>now run the server</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>uvicorn main:app
</code></pre></div></div>
<p>open your browser and visit <code class="highlighter-rouge">http://127.0.0.1:8000/</code>
you should see the following response:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="s2">"message"</span><span class="p">:</span><span class="s2">"Welcome to the world of FastAPI!"</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>visit <code class="highlighter-rouge">http://127.0.0.1:8000/items/apple?q=delicious</code>
you should see the below response:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="s2">"item"</span><span class="p">:</span><span class="s2">"apple"</span><span class="p">,</span><span class="s2">"q"</span><span class="p">:</span><span class="s2">"delicious"</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>that’s great, we have already created an API having two endpoints:</p>
<ul>
<li><code class="highlighter-rouge">http://127.0.0.1:8000/</code> doesn’t take any parameters and it simply returns a JSON response.</li>
<li><code class="highlighter-rouge">http://127.0.0.1:8000/items/{item}"</code>takes a parameter <code class="highlighter-rouge">item</code> of type <code class="highlighter-rouge">str</code> and optional <code class="highlighter-rouge">str</code> query parameter <code class="highlighter-rouge">q</code>.</li>
</ul>
<p>another good feature of FastAPI is that it provides an interactive API documentation, simply visit <code class="highlighter-rouge">http://127.0.0.1:8000/docs</code> or <code class="highlighter-rouge">http://127.0.0.1:8000/redoc</code>.</p>
<p>Now let’s go ahead and rebuild our polls tutorial API.
The endpoints we created above are static, they don’t interact with the database. In the next section you will learn how we can use <a href="https://www.sqlalchemy.org/">SQLAlchemy</a> for ORM and <a href="https://pydantic-docs.helpmanual.io/">Pydantic</a> to create models/schemas to make our APIs dynamic.</p>
<p>This post assumes that you’re familiar with <code class="highlighter-rouge">SQLAlchemy</code>, you can refer this <a href="https://www.sqlalchemy.org/">docs</a> for more details.</p>
<p>We will create the following endpoints</p>
<ul>
<li>An API to create poll question</li>
<li>API to list all poll questions</li>
<li>API to get question detail</li>
<li>API to edit poll question</li>
<li>API to delete poll question</li>
<li>API to create choice for a particular poll question</li>
<li>API to update votes for a particular question</li>
</ul>
<p>Our project structure would look like this</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">└───pollsapi</span><span class="w">
</span><span class="err">│---</span><span class="w"> </span><span class="err">crud.py</span><span class="w">
</span><span class="err">│---</span><span class="w"> </span><span class="err">database.py</span><span class="w">
</span><span class="err">│---</span><span class="w"> </span><span class="err">main.py</span><span class="w">
</span><span class="err">│---</span><span class="w"> </span><span class="err">models.py</span><span class="w">
</span><span class="err">│---</span><span class="w"> </span><span class="err">schemas.py</span><span class="w">
</span></code></pre></div></div>
<p>Now let’s add the following code to <code class="highlighter-rouge">pollsapi/database.py</code></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">sqlalchemy</span> <span class="kn">import</span> <span class="n">create_engine</span>
<span class="kn">from</span> <span class="nn">sqlalchemy.ext.declarative</span> <span class="kn">import</span> <span class="n">declarative_base</span>
<span class="kn">from</span> <span class="nn">sqlalchemy.orm</span> <span class="kn">import</span> <span class="n">sessionmaker</span>
<span class="n">SQLALCHEMY_DATABASE_URL</span> <span class="o">=</span> <span class="s">"postgresql://YOUR_USERNAME:YOUR_PASSWORD@localhost:5432/DATABASE_NAME"</span>
<span class="n">engine</span> <span class="o">=</span> <span class="n">create_engine</span><span class="p">(</span>
<span class="n">SQLALCHEMY_DATABASE_URL</span>
<span class="p">)</span>
<span class="n">SessionLocal</span> <span class="o">=</span> <span class="n">sessionmaker</span><span class="p">(</span><span class="n">autocommit</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="n">autoflush</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="n">bind</span><span class="o">=</span><span class="n">engine</span><span class="p">)</span>
<span class="n">Base</span> <span class="o">=</span> <span class="n">declarative_base</span><span class="p">()</span>
</code></pre></div></div>
<p>after that, add the following code to <code class="highlighter-rouge">pollsapi/models.py</code></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">sqlalchemy.ext.declarative</span> <span class="kn">import</span> <span class="n">declarative_base</span>
<span class="kn">from</span> <span class="nn">sqlalchemy</span> <span class="kn">import</span> <span class="n">Column</span><span class="p">,</span> <span class="n">String</span><span class="p">,</span> <span class="n">Integer</span><span class="p">,</span> <span class="n">DateTime</span><span class="p">,</span> <span class="n">ForeignKey</span>
<span class="kn">from</span> <span class="nn">sqlalchemy.orm</span> <span class="kn">import</span> <span class="n">relationship</span>
<span class="kn">from</span> <span class="nn">database</span> <span class="kn">import</span> <span class="n">Base</span>
<span class="k">class</span> <span class="nc">Question</span><span class="p">(</span><span class="n">Base</span><span class="p">):</span>
<span class="n">__tablename__</span> <span class="o">=</span> <span class="s">"question"</span>
<span class="nb">id</span> <span class="o">=</span> <span class="n">Column</span><span class="p">(</span><span class="n">Integer</span><span class="p">,</span> <span class="n">primary_key</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="n">question_text</span> <span class="o">=</span> <span class="n">Column</span><span class="p">(</span><span class="n">String</span><span class="p">(</span><span class="mi">200</span><span class="p">))</span>
<span class="n">pub_date</span> <span class="o">=</span> <span class="n">Column</span><span class="p">(</span><span class="n">DateTime</span><span class="p">)</span>
<span class="n">choices</span> <span class="o">=</span> <span class="n">relationship</span><span class="p">(</span><span class="s">'Choice'</span><span class="p">,</span> <span class="n">back_populates</span><span class="o">=</span><span class="s">"question"</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">Choice</span><span class="p">(</span><span class="n">Base</span><span class="p">):</span>
<span class="n">__tablename__</span> <span class="o">=</span> <span class="s">"choice"</span>
<span class="nb">id</span> <span class="o">=</span> <span class="n">Column</span><span class="p">(</span><span class="n">Integer</span><span class="p">,</span> <span class="n">primary_key</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="n">question_id</span> <span class="o">=</span> <span class="n">Column</span><span class="p">(</span><span class="n">Integer</span><span class="p">,</span> <span class="n">ForeignKey</span><span class="p">(</span><span class="s">'question.id'</span><span class="p">,</span> <span class="n">ondelete</span><span class="o">=</span><span class="s">'CASCADE'</span><span class="p">))</span>
<span class="n">choice_text</span> <span class="o">=</span> <span class="n">Column</span><span class="p">(</span><span class="n">String</span><span class="p">(</span><span class="mi">200</span><span class="p">))</span>
<span class="n">votes</span> <span class="o">=</span> <span class="n">Column</span><span class="p">(</span><span class="n">Integer</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
<span class="n">question</span> <span class="o">=</span> <span class="n">relationship</span><span class="p">(</span><span class="s">"Question"</span><span class="p">,</span> <span class="n">back_populates</span><span class="o">=</span><span class="s">"choices"</span><span class="p">)</span>
</code></pre></div></div>
<p>we have created <code class="highlighter-rouge">relationship</code> provided by SQLAlchemy ORM, with this we can simply access attribute like <code class="highlighter-rouge">question.choices</code> to get all the choices for that particular question. Similarly the we can refer <code class="highlighter-rouge">choice.question</code> to get question object related to that choice.</p>
<p>Ok, so far so good, we will now create schemas using the <code class="highlighter-rouge">pydantic</code> library.
Go ahead and add the following code to <code class="highlighter-rouge">pollsapi/schemas.py</code></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
<span class="kn">from</span> <span class="nn">pydantic</span> <span class="kn">import</span> <span class="n">BaseModel</span>
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span>
<span class="c"># Choice schema</span>
<span class="k">class</span> <span class="nc">ChoiceBase</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
<span class="n">choice_text</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">votes</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">class</span> <span class="nc">ChoiceCreate</span><span class="p">(</span><span class="n">ChoiceBase</span><span class="p">):</span>
<span class="k">pass</span>
<span class="k">class</span> <span class="nc">ChoiceList</span><span class="p">(</span><span class="n">ChoiceBase</span><span class="p">):</span>
<span class="nb">id</span><span class="p">:</span> <span class="nb">int</span>
<span class="k">class</span> <span class="nc">Config</span><span class="p">:</span>
<span class="n">orm_mode</span> <span class="o">=</span> <span class="bp">True</span>
<span class="c"># Question schema</span>
<span class="k">class</span> <span class="nc">QuestionBase</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
<span class="n">question_text</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">pub_date</span><span class="p">:</span> <span class="n">datetime</span>
<span class="k">class</span> <span class="nc">QuestionCreate</span><span class="p">(</span><span class="n">QuestionBase</span><span class="p">):</span>
<span class="k">pass</span>
<span class="k">class</span> <span class="nc">Question</span><span class="p">(</span><span class="n">QuestionBase</span><span class="p">):</span>
<span class="nb">id</span><span class="p">:</span> <span class="nb">int</span>
<span class="k">class</span> <span class="nc">Config</span><span class="p">:</span>
<span class="n">orm_mode</span> <span class="o">=</span> <span class="bp">True</span>
<span class="k">class</span> <span class="nc">QuestionInfo</span><span class="p">(</span><span class="n">Question</span><span class="p">):</span>
<span class="n">choices</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ChoiceList</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</code></pre></div></div>
<p>defining attributes in SQLAlchemy is different as compared with Pydantic, in SQLAlchemy arributes are defined using <code class="highlighter-rouge">=</code> and the type is passed as a parameter to <code class="highlighter-rouge">Column</code> like this</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>question_text = Column(String)
</code></pre></div></div>
<p>whereas the Pydantic style declares the type using <code class="highlighter-rouge">:</code> like this</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>question_text: str
</code></pre></div></div>
<p>Pyndatic models/schemas will be mapped to the incoming data (request data in POST, PUT) and to the response data returned from the API.</p>
<p>We have created base classes <code class="highlighter-rouge">QuestionBase</code> and <code class="highlighter-rouge">ChoiceBase</code> that extends pydantic <code class="highlighter-rouge">BaseModel</code> to hold attributes which are common for creating or reading data and created other classes that inherit from these base classes, the reason being we want specific attributes for creation and reading.</p>
<p>like for example - for creating a choice we need <code class="highlighter-rouge">choice_text</code> and <code class="highlighter-rouge">votes</code> (if not passed, it defaults to <strong>0</strong>) so we will use <code class="highlighter-rouge">ChoiceCreate</code> and for reading the choice, we want to return <code class="highlighter-rouge">id</code>, <code class="highlighter-rouge">choice_text</code> and <code class="highlighter-rouge">votes</code> and in this case we will use <code class="highlighter-rouge">ChoiceList</code>.</p>
<p>Another important thing to understand is the use of <code class="highlighter-rouge">orm_mode = True</code>, notice we have added a <code class="highlighter-rouge">class Config</code> and have set <code class="highlighter-rouge">orm_mode = True</code>, this is because by default Pydantic model could read the data from <code class="highlighter-rouge">dict</code> and it can’t read the data if the data is an ORM model so with the <code class="highlighter-rouge">orm_mode = True</code> added to our class, Pydantic model can also read the data from the object something like <code class="highlighter-rouge">data.question_text</code>.</p>
<p>Ok, we will now create <code class="highlighter-rouge">pollsapi/crud.py</code> which will contain all the functions to perform CRUD (<strong>C</strong>reate, <strong>R</strong>etrieve, <strong>U</strong>pdate and <strong>D</strong>elete) operations.</p>
<p>Add the following code to <code class="highlighter-rouge">pollsapi/crud.py</code></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">sqlalchemy.orm</span> <span class="kn">import</span> <span class="n">Session</span>
<span class="kn">from</span> <span class="nn">models</span> <span class="kn">import</span> <span class="n">Base</span><span class="p">,</span> <span class="n">Question</span><span class="p">,</span> <span class="n">Choice</span>
<span class="kn">import</span> <span class="nn">schema</span>
<span class="c"># Question</span>
<span class="k">def</span> <span class="nf">create_question</span><span class="p">(</span><span class="n">db</span><span class="p">:</span> <span class="n">Session</span><span class="p">,</span> <span class="n">question</span><span class="p">:</span> <span class="n">schema</span><span class="o">.</span><span class="n">QuestionCreate</span><span class="p">):</span>
<span class="n">obj</span> <span class="o">=</span> <span class="n">Question</span><span class="p">(</span><span class="o">**</span><span class="n">question</span><span class="o">.</span><span class="nb">dict</span><span class="p">())</span>
<span class="n">db</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span>
<span class="n">db</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
<span class="k">return</span> <span class="n">obj</span>
<span class="k">def</span> <span class="nf">get_all_questions</span><span class="p">(</span><span class="n">db</span><span class="p">:</span> <span class="n">Session</span><span class="p">):</span>
<span class="k">return</span> <span class="n">db</span><span class="o">.</span><span class="n">query</span><span class="p">(</span><span class="n">Question</span><span class="p">)</span><span class="o">.</span><span class="nb">all</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">get_question</span><span class="p">(</span><span class="n">db</span><span class="p">:</span><span class="n">Session</span><span class="p">,</span> <span class="n">qid</span><span class="p">):</span>
<span class="k">return</span> <span class="n">db</span><span class="o">.</span><span class="n">query</span><span class="p">(</span><span class="n">Question</span><span class="p">)</span><span class="o">.</span><span class="nb">filter</span><span class="p">(</span><span class="n">Question</span><span class="o">.</span><span class="nb">id</span> <span class="o">==</span> <span class="n">qid</span><span class="p">)</span><span class="o">.</span><span class="n">first</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">edit_question</span><span class="p">(</span><span class="n">db</span><span class="p">:</span> <span class="n">Session</span><span class="p">,</span> <span class="n">qid</span><span class="p">,</span> <span class="n">question</span><span class="p">:</span> <span class="n">schema</span><span class="o">.</span><span class="n">QuestionCreate</span><span class="p">):</span>
<span class="n">obj</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">query</span><span class="p">(</span><span class="n">Question</span><span class="p">)</span><span class="o">.</span><span class="nb">filter</span><span class="p">(</span><span class="n">Question</span><span class="o">.</span><span class="nb">id</span> <span class="o">==</span> <span class="n">qid</span><span class="p">)</span><span class="o">.</span><span class="n">first</span><span class="p">()</span>
<span class="n">obj</span><span class="o">.</span><span class="n">question_text</span> <span class="o">=</span> <span class="n">question</span><span class="o">.</span><span class="n">question_text</span>
<span class="n">obj</span><span class="o">.</span><span class="n">pub_date</span> <span class="o">=</span> <span class="n">question</span><span class="o">.</span><span class="n">pub_date</span>
<span class="n">db</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
<span class="k">return</span> <span class="n">obj</span>
<span class="k">def</span> <span class="nf">delete_question</span><span class="p">(</span><span class="n">db</span><span class="p">:</span> <span class="n">Session</span><span class="p">,</span> <span class="n">qid</span><span class="p">):</span>
<span class="n">db</span><span class="o">.</span><span class="n">query</span><span class="p">(</span><span class="n">Question</span><span class="p">)</span><span class="o">.</span><span class="nb">filter</span><span class="p">(</span><span class="n">Question</span><span class="o">.</span><span class="nb">id</span> <span class="o">==</span> <span class="n">qid</span><span class="p">)</span><span class="o">.</span><span class="n">delete</span><span class="p">()</span>
<span class="n">db</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
<span class="c"># Choice</span>
<span class="k">def</span> <span class="nf">create_choice</span><span class="p">(</span><span class="n">db</span><span class="p">:</span><span class="n">Session</span><span class="p">,</span> <span class="n">qid</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">choice</span><span class="p">:</span> <span class="n">schema</span><span class="o">.</span><span class="n">ChoiceCreate</span><span class="p">):</span>
<span class="n">obj</span> <span class="o">=</span> <span class="n">Choice</span><span class="p">(</span><span class="o">**</span><span class="n">choice</span><span class="o">.</span><span class="nb">dict</span><span class="p">(),</span> <span class="n">question_id</span><span class="o">=</span><span class="n">qid</span><span class="p">)</span>
<span class="n">db</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span>
<span class="n">db</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
<span class="k">return</span> <span class="n">obj</span>
<span class="k">def</span> <span class="nf">update_vote</span><span class="p">(</span><span class="n">choice_id</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">db</span><span class="p">:</span><span class="n">Session</span><span class="p">):</span>
<span class="n">obj</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">query</span><span class="p">(</span><span class="n">Choice</span><span class="p">)</span><span class="o">.</span><span class="nb">filter</span><span class="p">(</span><span class="n">Choice</span><span class="o">.</span><span class="nb">id</span> <span class="o">==</span> <span class="n">choice_id</span><span class="p">)</span><span class="o">.</span><span class="n">first</span><span class="p">()</span>
<span class="n">obj</span><span class="o">.</span><span class="n">votes</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="n">db</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
<span class="k">return</span> <span class="n">obj</span>
</code></pre></div></div>
<p>we have created all the utility functions which will be used in API functions.</p>
<p>Now comes the real file <code class="highlighter-rouge">pollsapi/main.py</code>, which will make use of all the files we created above.</p>
<h3 id="create-a-poll-question">Create a poll question</h3>
<p>Add the following lines to <code class="highlighter-rouge">pollsapi/main.py</code></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">fastapi</span> <span class="kn">import</span> <span class="n">FastAPI</span><span class="p">,</span> <span class="n">HTTPException</span><span class="p">,</span> <span class="n">Response</span><span class="p">,</span> <span class="n">Depends</span>
<span class="kn">import</span> <span class="nn">schema</span>
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span>
<span class="kn">from</span> <span class="nn">sqlalchemy.orm</span> <span class="kn">import</span> <span class="n">Session</span>
<span class="kn">import</span> <span class="nn">crud</span>
<span class="kn">from</span> <span class="nn">database</span> <span class="kn">import</span> <span class="n">SessionLocal</span><span class="p">,</span> <span class="n">engine</span>
<span class="kn">from</span> <span class="nn">models</span> <span class="kn">import</span> <span class="n">Base</span>
<span class="n">Base</span><span class="o">.</span><span class="n">metadata</span><span class="o">.</span><span class="n">create_all</span><span class="p">(</span><span class="n">bind</span><span class="o">=</span><span class="n">engine</span><span class="p">)</span>
<span class="n">app</span> <span class="o">=</span> <span class="n">FastAPI</span><span class="p">()</span>
<span class="c"># Dependency</span>
<span class="k">def</span> <span class="nf">get_db</span><span class="p">():</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">db</span> <span class="o">=</span> <span class="n">SessionLocal</span><span class="p">()</span>
<span class="k">yield</span> <span class="n">db</span>
<span class="k">finally</span><span class="p">:</span>
<span class="n">db</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
<span class="c">## Question</span>
<span class="nd">@app.post</span><span class="p">(</span><span class="s">"/questions/"</span><span class="p">,</span> <span class="n">response_model</span><span class="o">=</span><span class="n">schema</span><span class="o">.</span><span class="n">QuestionInfo</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">create_question</span><span class="p">(</span><span class="n">question</span><span class="p">:</span> <span class="n">schema</span><span class="o">.</span><span class="n">QuestionCreate</span><span class="p">,</span> <span class="n">db</span><span class="p">:</span> <span class="n">Session</span> <span class="o">=</span> <span class="n">Depends</span><span class="p">(</span><span class="n">get_db</span><span class="p">)):</span>
<span class="k">return</span> <span class="n">crud</span><span class="o">.</span><span class="n">create_question</span><span class="p">(</span><span class="n">db</span><span class="o">=</span><span class="n">db</span><span class="p">,</span> <span class="n">question</span><span class="o">=</span><span class="n">question</span><span class="p">)</span>
</code></pre></div></div>
<ul>
<li>This line <code class="highlighter-rouge">Base.metadata.create_all(bind=engine)</code> creates database tables by using the SQLAlchemy models we defined in <code class="highlighter-rouge">pollsapi/models.py</code>.</li>
<li>function <code class="highlighter-rouge">create_question</code>is decorated using the <code class="highlighter-rouge">app</code> object created above which is an instance of <code class="highlighter-rouge">FastAPI</code>, it takes two arguments <code class="highlighter-rouge">path</code> and <code class="highlighter-rouge">response_model</code>. <code class="highlighter-rouge">response_model</code> returns the schema <code class="highlighter-rouge">QuestionInfo</code> so the endpoint will return the fields <code class="highlighter-rouge">id</code>, <code class="highlighter-rouge">question_text</code> and <code class="highlighter-rouge">pub_date</code>.`</li>
<li>We have created a function <code class="highlighter-rouge">create_question</code>, first argument receives the request data and maps it to the schema <code class="highlighter-rouge">QuestionCreate</code> which has the fields <code class="highlighter-rouge">question_text</code> and <code class="highlighter-rouge">pub_date</code> and the second argument creates a session/request and then it gets closed after the request is completed.</li>
</ul>
<p>now visit <code class="highlighter-rouge">http://127.0.0.1:8000/docs</code> and you should see section to POST <code class="highlighter-rouge">/questions/</code> something like this</p>
<p><img src="/assets/images/fastapi/fastapidocs.png" alt="" /></p>
<p>click on that section and it will expand, now click on <code class="highlighter-rouge">Try it out</code> to test your API.</p>
<h3 id="list-all-poll-questions">List all poll questions</h3>
<p>We will now create an endpoint to get all our poll questions, for this we will use <code class="highlighter-rouge">@app.get</code>, add another function in <code class="highlighter-rouge">pollsapi/main.py</code></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@app.get</span><span class="p">(</span><span class="s">"/questions/"</span><span class="p">,</span> <span class="n">response_model</span><span class="o">=</span><span class="n">List</span><span class="p">[</span><span class="n">schema</span><span class="o">.</span><span class="n">Question</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">get_questions</span><span class="p">(</span><span class="n">db</span><span class="p">:</span> <span class="n">Session</span> <span class="o">=</span> <span class="n">Depends</span><span class="p">(</span><span class="n">get_db</span><span class="p">)):</span>
<span class="k">return</span> <span class="n">crud</span><span class="o">.</span><span class="n">get_all_questions</span><span class="p">(</span><span class="n">db</span><span class="o">=</span><span class="n">db</span><span class="p">)</span>
</code></pre></div></div>
<p>notice the use of <code class="highlighter-rouge">List</code> in <code class="highlighter-rouge">response_model</code>, <code class="highlighter-rouge">crud.get_all_questions</code> returns a list of objects and not just an object so we should let our framework know that. Try removing the <code class="highlighter-rouge">List</code> and our application will throw an error.</p>
<p>at this point when you visit <code class="highlighter-rouge">http://127.0.0.1:8000/docs</code>, you should see two sections - <code class="highlighter-rouge">POST /questions/</code> and <code class="highlighter-rouge">GET /questions/</code>, click on GET section and Try it out and you should see a response something like below</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="s2">"question_text"</span><span class="p">:</span><span class="w"> </span><span class="s2">"What is fastAPI?"</span><span class="p">,</span><span class="w">
</span><span class="s2">"pub_date"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2020-05-14T12:58:05.043000"</span><span class="p">,</span><span class="w">
</span><span class="s2">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></code></pre></div></div>
<h3 id="retrieve-edit-and-delete-a-poll-question">Retrieve, Edit and Delete a poll question</h3>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">get_question_obj</span><span class="p">(</span><span class="n">db</span><span class="p">,</span> <span class="n">qid</span><span class="p">):</span>
<span class="n">obj</span> <span class="o">=</span> <span class="n">crud</span><span class="o">.</span><span class="n">get_question</span><span class="p">(</span><span class="n">db</span><span class="o">=</span><span class="n">db</span><span class="p">,</span> <span class="n">qid</span><span class="o">=</span><span class="n">qid</span><span class="p">)</span>
<span class="k">if</span> <span class="n">obj</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
<span class="k">raise</span> <span class="n">HTTPException</span><span class="p">(</span><span class="n">status_code</span><span class="o">=</span><span class="mi">404</span><span class="p">,</span> <span class="n">detail</span><span class="o">=</span><span class="s">"Question not found"</span><span class="p">)</span>
<span class="k">return</span> <span class="n">obj</span>
<span class="nd">@app.get</span><span class="p">(</span><span class="s">"/questions/{qid}"</span><span class="p">,</span> <span class="n">response_model</span><span class="o">=</span><span class="n">schema</span><span class="o">.</span><span class="n">QuestionInfo</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">get_question</span><span class="p">(</span><span class="n">qid</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">db</span><span class="p">:</span> <span class="n">Session</span> <span class="o">=</span> <span class="n">Depends</span><span class="p">(</span><span class="n">get_db</span><span class="p">)):</span>
<span class="k">return</span> <span class="n">get_question_obj</span><span class="p">(</span><span class="n">db</span><span class="o">=</span><span class="n">db</span><span class="p">,</span> <span class="n">qid</span><span class="o">=</span><span class="n">qid</span><span class="p">)</span>
<span class="nd">@app.put</span><span class="p">(</span><span class="s">"/questions/{qid}"</span><span class="p">,</span> <span class="n">response_model</span><span class="o">=</span><span class="n">schema</span><span class="o">.</span><span class="n">QuestionInfo</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">edit_question</span><span class="p">(</span><span class="n">qid</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">question</span><span class="p">:</span> <span class="n">schema</span><span class="o">.</span><span class="n">QuestionCreate</span><span class="p">,</span> <span class="n">db</span><span class="p">:</span> <span class="n">Session</span> <span class="o">=</span> <span class="n">Depends</span><span class="p">(</span><span class="n">get_db</span><span class="p">)):</span>
<span class="n">get_question_obj</span><span class="p">(</span><span class="n">db</span><span class="o">=</span><span class="n">db</span><span class="p">,</span> <span class="n">qid</span><span class="o">=</span><span class="n">qid</span><span class="p">)</span>
<span class="n">obj</span> <span class="o">=</span> <span class="n">crud</span><span class="o">.</span><span class="n">edit_question</span><span class="p">(</span><span class="n">db</span><span class="o">=</span><span class="n">db</span><span class="p">,</span> <span class="n">qid</span><span class="o">=</span><span class="n">qid</span><span class="p">,</span> <span class="n">question</span><span class="o">=</span><span class="n">question</span><span class="p">)</span>
<span class="k">return</span> <span class="n">obj</span>
<span class="nd">@app.delete</span><span class="p">(</span><span class="s">"/questions/{qid}"</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">delete_question</span><span class="p">(</span><span class="n">qid</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">db</span><span class="p">:</span> <span class="n">Session</span> <span class="o">=</span> <span class="n">Depends</span><span class="p">(</span><span class="n">get_db</span><span class="p">)):</span>
<span class="n">get_question_obj</span><span class="p">(</span><span class="n">db</span><span class="o">=</span><span class="n">db</span><span class="p">,</span> <span class="n">qid</span><span class="o">=</span><span class="n">qid</span><span class="p">)</span>
<span class="n">crud</span><span class="o">.</span><span class="n">delete_question</span><span class="p">(</span><span class="n">db</span><span class="o">=</span><span class="n">db</span><span class="p">,</span> <span class="n">qid</span><span class="o">=</span><span class="n">qid</span><span class="p">)</span>
<span class="k">return</span> <span class="p">{</span><span class="s">"detail"</span><span class="p">:</span> <span class="s">"Question deleted"</span><span class="p">,</span> <span class="s">"status_code"</span><span class="p">:</span> <span class="mi">204</span><span class="p">}</span>
</code></pre></div></div>
<p>We have used different response_model for <code class="highlighter-rouge">get_questions</code> and <code class="highlighter-rouge">get_question</code>, this is because we wanted to show <code class="highlighter-rouge">choices</code> in the API response only in case of Question detail API and not for Question list API.</p>
<h3 id="api-to-create-choice-for-a-particular-poll-question">API to create choice for a particular poll question</h3>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@app.post</span><span class="p">(</span><span class="s">"/questions/{qid}/choice"</span><span class="p">,</span> <span class="n">response_model</span><span class="o">=</span><span class="n">schema</span><span class="o">.</span><span class="n">ChoiceList</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">create_choice</span><span class="p">(</span><span class="n">qid</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">choice</span><span class="p">:</span> <span class="n">schema</span><span class="o">.</span><span class="n">ChoiceCreate</span><span class="p">,</span> <span class="n">db</span><span class="p">:</span> <span class="n">Session</span> <span class="o">=</span> <span class="n">Depends</span><span class="p">(</span><span class="n">get_db</span><span class="p">)):</span>
<span class="n">get_question_obj</span><span class="p">(</span><span class="n">db</span><span class="o">=</span><span class="n">db</span><span class="p">,</span> <span class="n">qid</span><span class="o">=</span><span class="n">qid</span><span class="p">)</span>
<span class="k">return</span> <span class="n">crud</span><span class="o">.</span><span class="n">create_choice</span><span class="p">(</span><span class="n">db</span><span class="o">=</span><span class="n">db</span><span class="p">,</span> <span class="n">qid</span><span class="o">=</span><span class="n">qid</span><span class="p">,</span> <span class="n">choice</span><span class="o">=</span><span class="n">choice</span><span class="p">)</span>
</code></pre></div></div>
<p>and finally</p>
<h3 id="api-to-update-votes-for-a-particular-question">API to update votes for a particular question</h3>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@app.put</span><span class="p">(</span><span class="s">"/choices/{choice_id}/vote"</span><span class="p">,</span> <span class="n">response_model</span><span class="o">=</span><span class="n">schema</span><span class="o">.</span><span class="n">ChoiceList</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">update_vote</span><span class="p">(</span><span class="n">choice_id</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">db</span><span class="p">:</span> <span class="n">Session</span> <span class="o">=</span> <span class="n">Depends</span><span class="p">(</span><span class="n">get_db</span><span class="p">)):</span>
<span class="k">return</span> <span class="n">crud</span><span class="o">.</span><span class="n">update_vote</span><span class="p">(</span><span class="n">choice_id</span><span class="o">=</span><span class="n">choice_id</span><span class="p">,</span> <span class="n">db</span><span class="o">=</span><span class="n">db</span><span class="p">)</span>
</code></pre></div></div>
<p>the following are the endpoints for Question and Choice</p>
<ul>
<li>Create question - <code class="highlighter-rouge">POST http://127.0.0.1:8000/questions/</code></li>
<li>List all questions - <code class="highlighter-rouge">GET http://127.0.0.1:8000/questions/</code></li>
<li>Retrieve a particular question - <code class="highlighter-rouge">GET http://127.0.0.1:8000/questions/{qid}</code></li>
<li>Edit a particular question - <code class="highlighter-rouge">PUT http://127.0.0.1:8000/questions/{qid}</code></li>
<li>Delete a particular question - <code class="highlighter-rouge">DELETE http://127.0.0.1:8000/questions/{qid}</code></li>
<li>Create choice for a particular poll question - <code class="highlighter-rouge">POST http://127.0.0.1:8000/questions/{qid}/choice</code></li>
<li>Update votes for a particular question - <code class="highlighter-rouge">PUT http://127.0.0.1:8000/choices/{choice_id}/vote</code></li>
</ul>
<p>You can find a source code <a href="https://github.com/manjunath24/Polls-API">here</a></p>Manjunath HugarIn this blog post we are going to rebuild Django Polls tutorial API using FastAPI. What is FastAPI? FastAPI is a web framework for building APIs. As per its official page, `FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. It is easy to learn, fast and said to be high performance, on par with NodeJS and Go.` Installation Open your terminal and run pip install fastapi also need to install ASGI server pip install uvicorn thats all, now lets quicky create some endpoints, create a file main.py and add the following from fastapi import FastAPI app = FastAPI() @app.get("/") def index(): return {"message": "Welcome to the world of FastAPI!"} @app.get("/items/{item}") def read_item(item: str, q: str = None): return {"item": item, "q": q} now run the server uvicorn main:app open your browser and visit http://127.0.0.1:8000/ you should see the following response: {"message":"Welcome to the world of FastAPI!"} visit http://127.0.0.1:8000/items/apple?q=delicious you should see the below response: {"item":"apple","q":"delicious"} that’s great, we have already created an API having two endpoints: http://127.0.0.1:8000/ doesn’t take any parameters and it simply returns a JSON response. http://127.0.0.1:8000/items/{item}"takes a parameter item of type str and optional str query parameter q. another good feature of FastAPI is that it provides an interactive API documentation, simply visit http://127.0.0.1:8000/docs or http://127.0.0.1:8000/redoc. Now let’s go ahead and rebuild our polls tutorial API. The endpoints we created above are static, they don’t interact with the database. In the next section you will learn how we can use SQLAlchemy for ORM and Pydantic to create models/schemas to make our APIs dynamic. This post assumes that you’re familiar with SQLAlchemy, you can refer this docs for more details. We will create the following endpoints An API to create poll question API to list all poll questions API to get question detail API to edit poll question API to delete poll question API to create choice for a particular poll question API to update votes for a particular question Our project structure would look like this └───pollsapi │--- crud.py │--- database.py │--- main.py │--- models.py │--- schemas.py Now let’s add the following code to pollsapi/database.py from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker SQLALCHEMY_DATABASE_URL = "postgresql://YOUR_USERNAME:YOUR_PASSWORD@localhost:5432/DATABASE_NAME" engine = create_engine( SQLALCHEMY_DATABASE_URL ) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base() after that, add the following code to pollsapi/models.py from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, String, Integer, DateTime, ForeignKey from sqlalchemy.orm import relationship from database import Base class Question(Base): __tablename__ = "question" id = Column(Integer, primary_key=True) question_text = Column(String(200)) pub_date = Column(DateTime) choices = relationship('Choice', back_populates="question") class Choice(Base): __tablename__ = "choice" id = Column(Integer, primary_key=True) question_id = Column(Integer, ForeignKey('question.id', ondelete='CASCADE')) choice_text = Column(String(200)) votes = Column(Integer, default=0) question = relationship("Question", back_populates="choices") we have created relationship provided by SQLAlchemy ORM, with this we can simply access attribute like question.choices to get all the choices for that particular question. Similarly the we can refer choice.question to get question object related to that choice. Ok, so far so good, we will now create schemas using the pydantic library. Go ahead and add the following code to pollsapi/schemas.py from datetime import datetime from pydantic import BaseModel from typing import List # Choice schema class ChoiceBase(BaseModel): choice_text: str votes: int = 0 class ChoiceCreate(ChoiceBase): pass class ChoiceList(ChoiceBase): id: int class Config: orm_mode = True # Question schema class QuestionBase(BaseModel): question_text: str pub_date: datetime class QuestionCreate(QuestionBase): pass class Question(QuestionBase): id: int class Config: orm_mode = True class QuestionInfo(Question): choices: List[ChoiceList] = [] defining attributes in SQLAlchemy is different as compared with Pydantic, in SQLAlchemy arributes are defined using = and the type is passed as a parameter to Column like this question_text = Column(String) whereas the Pydantic style declares the type using : like this question_text: str Pyndatic models/schemas will be mapped to the incoming data (request data in POST, PUT) and to the response data returned from the API. We have created base classes QuestionBase and ChoiceBase that extends pydantic BaseModel to hold attributes which are common for creating or reading data and created other classes that inherit from these base classes, the reason being we want specific attributes for creation and reading. like for example - for creating a choice we need choice_text and votes (if not passed, it defaults to 0) so we will use ChoiceCreate and for reading the choice, we want to return id, choice_text and votes and in this case we will use ChoiceList. Another important thing to understand is the use of orm_mode = True, notice we have added a class Config and have set orm_mode = True, this is because by default Pydantic model could read the data from dict and it can’t read the data if the data is an ORM model so with the orm_mode = True added to our class, Pydantic model can also read the data from the object something like data.question_text. Ok, we will now create pollsapi/crud.py which will contain all the functions to perform CRUD (Create, Retrieve, Update and Delete) operations. Add the following code to pollsapi/crud.py from sqlalchemy.orm import Session from models import Base, Question, Choice import schema # Question def create_question(db: Session, question: schema.QuestionCreate): obj = Question(**question.dict()) db.add(obj) db.commit() return obj def get_all_questions(db: Session): return db.query(Question).all() def get_question(db:Session, qid): return db.query(Question).filter(Question.id == qid).first() def edit_question(db: Session, qid, question: schema.QuestionCreate): obj = db.query(Question).filter(Question.id == qid).first() obj.question_text = question.question_text obj.pub_date = question.pub_date db.commit() return obj def delete_question(db: Session, qid): db.query(Question).filter(Question.id == qid).delete() db.commit() # Choice def create_choice(db:Session, qid: int, choice: schema.ChoiceCreate): obj = Choice(**choice.dict(), question_id=qid) db.add(obj) db.commit() return obj def update_vote(choice_id: int, db:Session): obj = db.query(Choice).filter(Choice.id == choice_id).first() obj.votes += 1 db.commit() return obj we have created all the utility functions which will be used in API functions. Now comes the real file pollsapi/main.py, which will make use of all the files we created above. Create a poll question Add the following lines to pollsapi/main.py from fastapi import FastAPI, HTTPException, Response, Depends import schema from typing import List from sqlalchemy.orm import Session import crud from database import SessionLocal, engine from models import Base Base.metadata.create_all(bind=engine) app = FastAPI() # Dependency def get_db(): try: db = SessionLocal() yield db finally: db.close() ## Question @app.post("/questions/", response_model=schema.QuestionInfo) def create_question(question: schema.QuestionCreate, db: Session = Depends(get_db)): return crud.create_question(db=db, question=question) This line Base.metadata.create_all(bind=engine) creates database tables by using the SQLAlchemy models we defined in pollsapi/models.py. function create_questionis decorated using the app object created above which is an instance of FastAPI, it takes two arguments path and response_model. response_model returns the schema QuestionInfo so the endpoint will return the fields id, question_text and pub_date.` We have created a function create_question, first argument receives the request data and maps it to the schema QuestionCreate which has the fields question_text and pub_date and the second argument creates a session/request and then it gets closed after the request is completed. now visit http://127.0.0.1:8000/docs and you should see section to POST /questions/ something like this click on that section and it will expand, now click on Try it out to test your API. List all poll questions We will now create an endpoint to get all our poll questions, for this we will use @app.get, add another function in pollsapi/main.py @app.get("/questions/", response_model=List[schema.Question]) def get_questions(db: Session = Depends(get_db)): return crud.get_all_questions(db=db) notice the use of List in response_model, crud.get_all_questions returns a list of objects and not just an object so we should let our framework know that. Try removing the List and our application will throw an error. at this point when you visit http://127.0.0.1:8000/docs, you should see two sections - POST /questions/ and GET /questions/, click on GET section and Try it out and you should see a response something like below [ { "question_text": "What is fastAPI?", "pub_date": "2020-05-14T12:58:05.043000", "id": 1 } ] Retrieve, Edit and Delete a poll question def get_question_obj(db, qid): obj = crud.get_question(db=db, qid=qid) if obj is None: raise HTTPException(status_code=404, detail="Question not found") return obj @app.get("/questions/{qid}", response_model=schema.QuestionInfo) def get_question(qid: int, db: Session = Depends(get_db)): return get_question_obj(db=db, qid=qid) @app.put("/questions/{qid}", response_model=schema.QuestionInfo) def edit_question(qid: int, question: schema.QuestionCreate, db: Session = Depends(get_db)): get_question_obj(db=db, qid=qid) obj = crud.edit_question(db=db, qid=qid, question=question) return obj @app.delete("/questions/{qid}") def delete_question(qid: int, db: Session = Depends(get_db)): get_question_obj(db=db, qid=qid) crud.delete_question(db=db, qid=qid) return {"detail": "Question deleted", "status_code": 204} We have used different response_model for get_questions and get_question, this is because we wanted to show choices in the API response only in case of Question detail API and not for Question list API. API to create choice for a particular poll question @app.post("/questions/{qid}/choice", response_model=schema.ChoiceList) def create_choice(qid: int, choice: schema.ChoiceCreate, db: Session = Depends(get_db)): get_question_obj(db=db, qid=qid) return crud.create_choice(db=db, qid=qid, choice=choice) and finally API to update votes for a particular question @app.put("/choices/{choice_id}/vote", response_model=schema.ChoiceList) def update_vote(choice_id: int, db: Session = Depends(get_db)): return crud.update_vote(choice_id=choice_id, db=db) the following are the endpoints for Question and Choice Create question - POST http://127.0.0.1:8000/questions/ List all questions - GET http://127.0.0.1:8000/questions/ Retrieve a particular question - GET http://127.0.0.1:8000/questions/{qid} Edit a particular question - PUT http://127.0.0.1:8000/questions/{qid} Delete a particular question - DELETE http://127.0.0.1:8000/questions/{qid} Create choice for a particular poll question - POST http://127.0.0.1:8000/questions/{qid}/choice Update votes for a particular question - PUT http://127.0.0.1:8000/choices/{choice_id}/vote You can find a source code here