|
|
| Line 1: |
Line 1: |
| − | FastAPI official documentation: https://fastapi.tiangolo.com
| + | ~ Migrated |
| − | | |
| − | Entrevista al creador de FastAPI Sebastién Ramirez: https://www.youtube.com/watch?v=QzuGFU6n_Gs
| |
| − | | |
| − | | |
| − | To run the fastapi app:
| |
| − | <syntaxhighlight lang="python3">
| |
| − | fastapi run main.py # With the new[standard] version - fastapi[standard]==0.112.0
| |
| − | uvicorn main:app --reload # It used to be done this way
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | | |
| − | <br />
| |
| − | ==Deployment==
| |
| − | https://fastapi.tiangolo.com/deployment/
| |
| − | | |
| − | | |
| − | '''Cloning the project from GitHub'''
| |
| − | <syntaxhighlight lang="shell">
| |
| − | git clone git@github.com:adeloaleman/fastapi.git
| |
| − | cd fastapi
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | | |
| − | '''Creating the venv and installing libraries'''
| |
| − | ...
| |
| − | | |
| − | | |
| − | '''Testing on the terminal'''
| |
| − | <syntaxhighlight lang="shell">
| |
| − | fastapi run main.py --port 8000 # from the api directory: fastapi/api/
| |
| − | | |
| − | uvicorn api.main:app --reload --host 0.0.0.0 --port 8000 # from the project root directory: fastapi/
| |
| − | | |
| − | gunicorn -w 4 -k uvicorn.workers.UvicornWorker api.main:app --bind 0.0.0.0:8000 # from the project root directory: fastapi/
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | | |
| − | '''Using systemd''': A Linux service manager that starts, stops, and monitors long-running processes. We use it to keep the FastAPI app running in the background, start it automatically on reboot, and restart it if it crashes.
| |
| − | | |
| − | Make sure to specify the user at "User=root"
| |
| − | | |
| − | <syntaxhighlight lang="shell">
| |
| − | ls /etc/systemd/system
| |
| − | | |
| − | sudo vi /etc/systemd/system/fastapi.service
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | <syntaxhighlight lang="shell">
| |
| − | [Unit]
| |
| − | Description=fastapi
| |
| − | After=network.target
| |
| − | | |
| − | [Service]
| |
| − | User=root
| |
| − | Group=www-data
| |
| − | WorkingDirectory=/var/www/sinfronteras/webapp-1/fastapi
| |
| − | Environment="PATH=/var/www/sinfronteras/webapp-1/fastapi/.venv/bin"
| |
| − | ExecStart=/var/www/sinfronteras/webapp-1/fastapi/.venv/bin/gunicorn -w 4 -k uvicorn.workers.UvicornWorker api.main:app --bind 0.0.0.0:8000
| |
| − | | |
| − | [Install]
| |
| − | WantedBy=multi-user.target
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | <syntaxhighlight lang="shell">
| |
| − | sudo systemctl daemon-reload
| |
| − | sudo systemctl start fastapi
| |
| − | sudo systemctl enable fastapi
| |
| − | sudo systemctl status fastapi
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | | |
| − | '''Now we'll use Nginx as a reverse proxy''': It sits in front of our FastAPI app so it can be accessed on port 80. Nginx handles client connections and forwards requests to our app running on Gunicorn at port 8000.
| |
| − | | |
| − | <syntaxhighlight lang="shell">
| |
| − | sudo vi /etc/nginx/sites-available/default
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | <syntaxhighlight lang="shell">
| |
| − | server {
| |
| − | listen 80;
| |
| − | server_name api.webapp.sinfronteras.ws;
| |
| − | | |
| − | location / {
| |
| − | proxy_pass http://127.0.0.1:8000;
| |
| − | proxy_set_header Host $host;
| |
| − | proxy_set_header X-Real-IP $remote_addr;
| |
| − | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
| |
| − | }
| |
| − | }
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | <syntaxhighlight lang="shell">
| |
| − | sudo nginx -t
| |
| − | sudo systemctl restart nginx
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | | |
| − | '''Adding HTTPS with Let’s Encrypt'''
| |
| − | | |
| − | <syntaxhighlight lang="shell">
| |
| − | sudo apt install certbot python3-certbot-nginx
| |
| − | | |
| − | sudo certbot --nginx -d api.webapp.sinfronteras.ws -d www.api.webapp.sinfronteras.ws
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | The following step is not mandatory. It is only to ensure everything is in order so the certificate can renew:
| |
| − | <syntaxhighlight lang="shell">
| |
| − | sudo certbot renew --dry-run
| |
| − | systemctl list-timers | grep certbot # This should display something like "certbot.timer"
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | | |
| − | We now need to modify our Ngnix configuration:
| |
| − | | |
| − | <syntaxhighlight lang="shell">
| |
| − | sudo vi /etc/nginx/sites-available/default
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | <syntaxhighlight lang="shell">
| |
| − | server {
| |
| − | listen 443 ssl;
| |
| − | server_name api.webapp.sinfronteras.ws www.api.webapp.sinfronteras.ws;
| |
| − | | |
| − | ssl_certificate /etc/letsencrypt/live/api.webapp.sinfronteras.ws/fullchain.pem;
| |
| − | ssl_certificate_key /etc/letsencrypt/live/api.webapp.sinfronteras.ws/privkey.pem;
| |
| − | | |
| − | location / {
| |
| − | proxy_pass http://127.0.0.1:8000;
| |
| − | proxy_set_header Host $host;
| |
| − | proxy_set_header X-Real-IP $remote_addr;
| |
| − | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
| |
| − | }
| |
| − | }
| |
| − | | |
| − | server {
| |
| − | listen 80;
| |
| − | server_name api.webapp.sinfronteras.ws www.api.webapp.sinfronteras.ws;
| |
| − | return 301 https://$host$request_uri;
| |
| − | }
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | <syntaxhighlight lang="shell">
| |
| − | sudo nginx -t
| |
| − | sudo systemctl restart nginx
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | | |
| − | '''To remove the deployment process'''
| |
| − | <syntaxhighlight lang="shell">
| |
| − | sudo systemctl stop fastapi
| |
| − | sudo systemctl disable fastapi
| |
| − | | |
| − | sudo rm /etc/systemd/system/fastapi.service
| |
| − | | |
| − | sudo systemctl daemon-reload
| |
| − | sudo systemctl reset-failed
| |
| − | | |
| − | | |
| − | sudo vi /etc/nginx/sites-available/default # We remove the configuration related to our app
| |
| − | sudo nginx -t
| |
| − | sudo systemctl reload nginx
| |
| − | sudo systemctl restart nginx # Alternative (full restart, slightly more disruptive)
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | | |
| − | <br />
| |
| − | | |
| − | ==Deployment Nextjs==
| |
| − | | |
| − | Note: In my Ubuntu 18 server tuve que instalar Node.js 16 (que es el más reciente compatible con Ubuntu 18) y Next.js 13 ("next": "^13.5.0") (que es el más reciente compatible con Node.js 16)
| |
| − | | |
| − | | |
| − | '''Cloning the project from GitHub'''
| |
| − | <syntaxhighlight lang="shell">
| |
| − | git clone git@github.com:adeloaleman/nextjs.git
| |
| − | cd nextjs
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | | |
| − | '''Installing libraries'''
| |
| − | <syntaxhighlight lang="shell">
| |
| − | npm install
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | | |
| − | '''Prepares our app for production''': It compiles, bundles, and optimizes everything so it's ready to deploy.
| |
| − | <syntaxhighlight lang="shell">
| |
| − | npm run build
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | | |
| − | '''Testing on the terminal'''
| |
| − | <syntaxhighlight lang="shell">
| |
| − | npm start # Port 3000 by default
| |
| − | PORT=3001 npm start # If we want to specify the port
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | | |
| − | '''PM2 process manager'''
| |
| − | <syntaxhighlight lang="shell">
| |
| − | sudo npm install -g pm2
| |
| − | | |
| − | # Start Next.js with PM2:
| |
| − | pm2 start npm --name "nextjs" -- start # Port 3000 by default
| |
| − | PORT=3001 pm2 start npm --name "nextjs" -- start # If we want to specify the port
| |
| − | | |
| − | pm2 save # Save the process
| |
| − | | |
| − | pm2 startup # Then we have to run the output returned by "pm2 startup". This is to be ran only once. So the first time we deploy an app with PM2. Not for every app.
| |
| − | pm2 save # Save again
| |
| − | | |
| − | # To verify it worked:
| |
| − | pm2 status
| |
| − | pm2 list
| |
| − | systemctl status pm2-youruser
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | | |
| − | '''Now we'll use Nginx as a reverse proxy''': It sits in front of our Next.js app so it can be accessed on port 80. Nginx handles client connections and forwards requests to our app running on PM2 at port 3000.
| |
| − | <syntaxhighlight lang="shell">
| |
| − | sudo vi /etc/nginx/sites-available/default
| |
| − | | |
| − | server {
| |
| − | listen 80;
| |
| − | server_name webapp.sinfronteras.ws www.webapp.sinfronteras.ws;
| |
| − | | |
| − | location / {
| |
| − | proxy_pass http://localhost:3000;
| |
| − | proxy_http_version 1.1;
| |
| − | proxy_set_header Upgrade $http_upgrade;
| |
| − | proxy_set_header Connection 'upgrade';
| |
| − | proxy_set_header Host $host;
| |
| − | proxy_cache_bypass $http_upgrade;
| |
| − | }
| |
| − | }
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | <syntaxhighlight lang="shell">
| |
| − | sudo nginx -t
| |
| − | sudo systemctl reload nginx
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | | |
| − | '''Adding HTTPS with Let's Encrypt'''
| |
| − | <syntaxhighlight lang="shell">
| |
| − | sudo apt install certbot python3-certbot-nginx
| |
| − | | |
| − | sudo certbot --nginx -d webapp.sinfronteras.ws -d www.webapp.sinfronteras.ws
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | The following step is not mandatory. It is only to ensure everything is in order so the certificate can renew:
| |
| − | <syntaxhighlight lang="shell">
| |
| − | sudo certbot renew --dry-run
| |
| − | systemctl list-timers | grep certbot # This should display something like "certbot.timer"
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | | |
| − | We now need to modify our Ngnix configuration:
| |
| − | <syntaxhighlight lang="shell">
| |
| − | server {
| |
| − | listen 443 ssl;
| |
| − | server_name webapp.sinfronteras.ws www.webapp.sinfronteras.ws;
| |
| − | | |
| − | ssl_certificate /etc/letsencrypt/live/webapp.sinfronteras.ws/fullchain.pem;
| |
| − | ssl_certificate_key /etc/letsencrypt/live/webapp.sinfronteras.ws/privkey.pem;
| |
| − | | |
| − | location / {
| |
| − | proxy_pass http://localhost:3000;
| |
| − | proxy_http_version 1.1;
| |
| − | proxy_set_header Upgrade $http_upgrade;
| |
| − | proxy_set_header Connection 'upgrade';
| |
| − | proxy_set_header Host $host;
| |
| − | proxy_cache_bypass $http_upgrade;
| |
| − | }
| |
| − | }
| |
| − | | |
| − | server {
| |
| − | listen 80;
| |
| − | server_name webapp.sinfronteras.ws www.webapp.sinfronteras.ws;
| |
| − | return 301 https://$host$request_uri;
| |
| − | }
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | <syntaxhighlight lang="shell">
| |
| − | sudo nginx -t
| |
| − | sudo systemctl reload nginx
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | | |
| − | '''To remove the deployment process'''
| |
| − | | |
| − | <syntaxhighlight lang="shell">
| |
| − | pm2 stop nextjs # Stop the process
| |
| − | pm2 delete nextjs # Remove it from PM2
| |
| − | pm2 save # Update saved process list
| |
| − | pm2 unstartup # [optional, if we ran pm2 startup]. Remove PM2 from startup. This affects the entire PM2 setup, not just nextjs, so we should NOT run it if we have other apps managed by PM2.
| |
| − | | |
| − | sudo vi /etc/nginx/sites-available/default # We remove the configuration related to our app
| |
| − | sudo nginx -t
| |
| − | sudo systemctl reload nginx
| |
| − | sudo systemctl restart nginx # Alternative (full restart, slightly more disruptive)
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | | |
| − | <br />
| |
| − | | |
| − | ==Deploy FastAPI, React, PostgreSQL Apps on AWS==
| |
| − | This is a Udemy courser: https://www.udemy.com/course/deploy-fastapi-fullstack-amazon-cloud-aws/?couponCode=ST17MT70725B
| |
| − | | |
| − | | |
| − | '''Project overview''':
| |
| − | https://www.udemy.com/course/deploy-fastapi-fullstack-amazon-cloud-aws/learn/lecture/44338842#overview
| |
| − | | |
| − | [[File:Deploying_FastAPI__React_app-Overview.png|600px|thumb|center|No sé si ésta es la mejor representación de la arquitectura final]]
| |
| − | | |
| − | | |
| − | <br />
| |
| − | ===AWS Core Services===
| |
| − | https://www.udemy.com/course/deploy-fastapi-fullstack-amazon-cloud-aws/learn/lecture/44338988#overview
| |
| − | | |
| − | | |
| − | * '''Budget'''
| |
| − | | |
| − | * '''EBN''' (Elastic Beanstalk)
| |
| − | :* End-to-end web application management. Managed service (PaaS) by AWS.
| |
| − | :* Allows us to automatically create the EC2s, ELB, S3 and Health Monitoring for our application.
| |
| − | :* We will be utilizing EBN to create the environment we need to deploy our FastAPI application.
| |
| − | | |
| − | * '''EC2''' (Elastic Compute Cloud)
| |
| − | :* Virtual Machines (EC2)
| |
| − | :* Auto Scaling Groups (ASG)
| |
| − | | |
| − | * '''ELB''' (Elastic Load Balancing)
| |
| − | :* Automatically distribute incoming application traf c to your deployed application.
| |
| − | :* Allows for scalability and traf c distribution.
| |
| − | | |
| − | * '''Health Monitoring''':
| |
| − | :* Make sure our application is healthy and deployed.
| |
| − | :* Reports when something is wrong, and when you need to take action.
| |
| − | | |
| − | * '''RDS''' (Relational Database Storage)
| |
| − | :* Allows you to create databases in the cloud and managed by AWS. Since it is managed by AWS, they will handle provisioning, OS Updates, backups, replications, scalability and more!
| |
| − | | |
| − | :*If you don't want AWS to handle your database infrastructure, you could create an EC2
| |
| − | ::* Deploy your database on the EC2
| |
| − | ::* Handle all upgrades yourself
| |
| − | ::* Handle all security yourself
| |
| − | ::* Handle all backups yourself
| |
| − | | |
| − | * '''Code Pipeline''':
| |
| − | :* CI/CD for our backend application (FastAPI).
| |
| − | :* Will connect to our GitHub account to find new pushes to the head.
| |
| − | :* Automatic deployments to our Elastic Beanstalk.
| |
| − | | |
| − | * '''Amplify'''
| |
| − | :* Deploy our SPA applications efficiently (Works with React).
| |
| − | :* Allow us to implement CI/CD for our front end application.
| |
| − | :* Store front end environmental variables.
| |
| − | | |
| − | * '''Route53''':
| |
| − | :* Route 53 is AWS scalable Domain Name System (DNS) web service.
| |
| − | | |
| − | * '''ACM''' (AWS Certificate Manager)
| |
| − | :* Manage and deploy SSL/TLS certificates. This makes your app go from HTTP -> HTTPS. ACM removes the process of uploading & renewing certificates.
| |
| − | | |
| − | | |
| − | <br />
| |
| − | ===FastAPI deployment===
| |
| − | | |
| − | [[File:Deploying FastAPI React app-FastAPI Infrastructure.png|600px|thumb|center|FastAPI Infrastructure: Ésta '''NO''' es la mejor representación del la arquitectura final]]
| |
| − | | |
| − | | |
| − | <br />
| |
| − | ====Create the FastAPI Infrastructure using Elastic Beanstalk EBN====
| |
| − | https://www.udemy.com/course/deploy-fastapi-fullstack-amazon-cloud-aws/learn/lecture/44340994#overview
| |
| − | | |
| − | https://www.udemy.com/course/deploy-fastapi-fullstack-amazon-cloud-aws/learn/lecture/44339038#overview
| |
| − | | |
| − | To deploy our FastAPI application from scratch we could create and configure EC2 VMs and an ELB. However, the '''EBN''' service (PaaS) allows us to automatically create the environment needed to deploy our FastAPI application. It abstracts the creation of EC2s, ELB, S3, and health monitoring components.
| |
| − | | |
| − | <blockquote>
| |
| − | '''Elastic Beanstalk''':
| |
| − | * Createte application:
| |
| − | :* Name: Webapp-fastapi-1
| |
| − | * Create environment:
| |
| − | :* Configure environment:
| |
| − | ::* Environment tier: Web Server environment
| |
| − | ::* Name: Webapp-fastapi-1-env
| |
| − | ::* We can enter a Domain name: webapp-fastapi-1
| |
| − | ::* Platform: Python
| |
| − | ::* Application code: We're gonna leave it as "Sample application" since we're going to implement CI/CD using Code Pipeline
| |
| − | ::* Presets: Single instance (free tier eligible)
| |
| − | :* Configure service access:
| |
| − | ::* Service role > Create role: This creates an IAM service role so we are giving Elastic Beanstalk permission to create the resources it needs (EC2 instance, S3 bucket, etc)
| |
| − | :::* Trusted entity type: AWS service
| |
| − | :::* Service or use case: Elastic Beanstalk
| |
| − | :::* Use case: Elastic Beanstalk - Environment
| |
| − | :::* Permissions policies: These should be the ones automatically proposed by the menu: AWSElasticBeanstalkEnhancedHealth / AWSElasticBeanstalkManagedUpdatesCustomerRolePolicy
| |
| − | :::* Role name: webapp-fastapi-1-aws-elasticbeanstalk-service-role
| |
| − | ::* EC2 instance profile > Create role: This creates an IAM instance profile with managed policies that allow your EC2 instances to perform required operations.
| |
| − | :::* Trusted entity type: AWS service
| |
| − | :::* Service or use case: Elastic Beanstalk
| |
| − | :::* Use case: Elastic Beanstalk - Compute
| |
| − | :::* Permissions policies: These should be the ones automatically proposed by the menu: AWSElasticBeanstalkMulticontainerDocker / AWSElasticBeanstalkWebTier / AWSElasticBeanstalkWorkerTier
| |
| − | :::* Role name: webapp-fastapi-1-aws-elasticbeanstalk-ec2-role
| |
| − | :::* '''Note that on the Udemy course''' https://www.udemy.com/course/deploy-fastapi-fullstack-amazon-cloud-aws/learn/lecture/44339038#overview it says to select "EC2" as "Service or use case" and also as "Use case". However, I think this changed after the course was released.
| |
| − | ::* EC2 key pair: We choose our key
| |
| − | :* Set up networking, database, and tags: This step can be skipped in our case
| |
| − | :* Configure instance traffic and scaling:
| |
| − | ::* Root volume type: General Purpose 3(SSD)
| |
| − | ::* To reduce cost we can:
| |
| − | ::: Fleet composition: Spot instance
| |
| − | ::: Spot allocation strategy: Lowest price
| |
| − | ::: Architecture: arm64
| |
| − | ::: Instance types: t4g.nano
| |
| − | ::* All the rest as default and click next
| |
| − | :* Configure updates, monitoring, and logging: Here we only need to configure our Environment properties:
| |
| − | ::* AUTH_SECRET_KEY = ****
| |
| − | ::* AUTH_ALGORITHM = HS256
| |
| − | ::* API_URL = http://localhost:3000 - This value is going to be changed when we deploy our frontend app but for now we can just leave it as localhost
| |
| − | ::* DB_URL - This is in case we're using an AWS DB service. If we keep SQLite for testing purposes, we can avoid this.
| |
| − | ::* DEPLOYMENT_ENVIRONMENT - This can also be configured if needed
| |
| − | | |
| − | | |
| − | [[File:Elastic_beanstalk-environment.png|600px|thumb|center|[[:File:Elastic_beanstalk-environment.png]]]]
| |
| − | | |
| − | | |
| − | </blockquote>
| |
| − | | |
| − | | |
| − | <br />
| |
| − | ====Deploy our FastAPI app and implement CI/CD using Code Pipeline====
| |
| − | https://www.udemy.com/course/deploy-fastapi-fullstack-amazon-cloud-aws/learn/lecture/44339044#overview
| |
| − | | |
| − | We'll use AWS CodePipeline to deploy our FastAPI app and implement CI/CD. This will allow us to connect to our '''GitHub''' account and automatically deploy code updates to '''EBN'''. Every time we update the GitHub repository, the changes will be reflected on EBN.
| |
| − | | |
| − | <blockquote>
| |
| − | | |
| − | '''CodePipeline''':
| |
| − | * Create Pipeline:
| |
| − | :* Creation option:
| |
| − | ::* Build custom pipeline
| |
| − | :* Pipeline settings:
| |
| − | ::* Name: Webapp-fastapi-1-pipeline
| |
| − | ::* Execution mode: Queued
| |
| − | ::* Service role - New service role:
| |
| − | :::* Role name: webapp-fastapi-1-AWSCodePipelineServiceRole-us-west-1
| |
| − | :::* '''Then we need to go to IAM''' > Roles > webapp-fastapi-1-AWSCodePipelineServiceRole-us-west-1 > Permissions > Add permission > Attach policies and add '''AdministratorAccess-AWSElasticBeanstalk'''
| |
| − | :* Add source stage:
| |
| − | ::* Source provider: GitHub (via Github App)
| |
| − | ::* Connection > Connect to GitHub: '''Here we need to make sure we click on "Install a new GitHub app"'''. If we don't do this, the pipeline code won't be automatically updated when we push changes to our GitHub repository. I think is good practice to use "adeloaleman@gmail.com" as connection name
| |
| − | ::* Repository name: adeloaleman/fastapi
| |
| − | ::* Default branch: main
| |
| − | ::* Output artifact format: CodePipeline default
| |
| − | ::* Webhook events: We don't need to add any filter
| |
| − | :* Add build stage - optional: Skip build stage
| |
| − | :* Add test stage - optional: Skip test stage
| |
| − | :* Add deploy stage:
| |
| − | ::* Deploy provider: AWS Elastic Beanstalk
| |
| − | ::* Input artifacts: We can leave as default (SourceArtifact)
| |
| − | ::* Application name: Webapp-fastapi-1
| |
| − | ::* Environment name: Webapp-fastapi-1-env
| |
| − | | |
| − | | |
| − | To avoid error during the deployment, such as "'''The provided role does not have the elasticbeanstalk:CreateApplicationVersion permission'''", we need to make sure to attach the '''AdministratorAccess-AWSElasticBeanstalk''' managed policy to the '''webapp-fastapi-1-AWSCodePipelineServiceRole-us-west-1''' role. However, it seems that the best and secure way of resolving those errors is by creating a custom inline policy and attaching it to the '''webapp-fastapi-1-AWSCodePipelineServiceRole-us-west-1''' role. For example:
| |
| − | :<code>ElasticBeanstalkCreateAppVersion</code>
| |
| − | :<syntaxhighlight lang="json">
| |
| − | {
| |
| − | "Version": "2012-10-17",
| |
| − | "Statement": [
| |
| − | {
| |
| − | "Sid": "AllowEBApplicationVersionCreation",
| |
| − | "Effect": "Allow",
| |
| − | "Action": [
| |
| − | "elasticbeanstalk:CreateApplicationVersion",
| |
| − | "elasticbeanstalk:UpdateEnvironment"
| |
| − | ],
| |
| − | "Resource": "*"
| |
| − | }
| |
| − | ]
| |
| − | }
| |
| − | </syntaxhighlight>
| |
| − | : See https://repost.aws/questions/QUcanRT07xScCtGQHYgoZvZQ/elasticbeanstalk-createapplicationversion-permission-error-pipeline-deploy-stage
| |
| − | : However, the '''ElasticBeanstalkCreateAppVersion''' custom inline policy resolved some, but not all, of the errors. Additional permissions appear to be missing.
| |
| − | | |
| − | | |
| − | '''We also need to remember that''' the FastAPI project requires a '''Procfile''' so that EBN knows how to run the server using Uvicorn. Its main purpose is to declare the command that should be used to start the web application.
| |
| − | | |
| − | : <code>Procfile</code>
| |
| − | : <syntaxhighlight lang="bash">
| |
| − | web: gunicorn -w 2 -k uvicorn.workers.UvicornWorker api.main:app
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | | |
| − | [[File:Connect_to_GitHub.png|600px|thumb|center|]]
| |
| − | | |
| − | [[File:CodePipeline.png|600px|thumb|center|]]
| |
| − | | |
| − | </blockquote>
| |
| − | | |
| − | | |
| − | <br />
| |
| − | ====Set up a custom domain via Route 53====
| |
| − | https://www.udemy.com/course/deploy-fastapi-fullstack-amazon-cloud-aws/learn/lecture/44341012#overview
| |
| − | | |
| − | | |
| − | * '''Route 53''' is the AWS Domain Name System (DNS) web service.
| |
| − | :* '''Register neww domain''': Route 53 > Registered domains > Register domains
| |
| − | | |
| − | :* '''Customize policies for the DNS'''.
| |
| − | | |
| − | :* '''Create record (subdomains)''': https://www.udemy.com/course/deploy-fastapi-fullstack-amazon-cloud-aws/learn/lecture/44339056#overview
| |
| − | :: Route 53 > Hosted zones > sinfrontera.net:
| |
| − | ::* Create record:
| |
| − | :::* Record name: api.webapp.sinfrontera.net
| |
| − | :::* Record type: A - Routes traffic to an IPv4 address and some AWS resources
| |
| − | :::* Alias: ✅
| |
| − | :::* Route traffic to:
| |
| − | :::: Alias to Elastic Beanstalk environment
| |
| − | :::: Environement: webapp-fastapi-1.us-west-1.elasticbeanstalk.com
| |
| − | :::* Routing policy: Simple routing
| |
| − | :::* Evaluate target health: No
| |
| − | | |
| − | | |
| − | <br />
| |
| − | ====Manage and Deploy SSL/TSL certificates so the app is available from HTTPS====
| |
| − | https://www.udemy.com/course/deploy-fastapi-fullstack-amazon-cloud-aws/learn/lecture/44341022#overview
| |
| − | https://www.udemy.com/course/deploy-fastapi-fullstack-amazon-cloud-aws/learn/lecture/44339066#overview
| |
| − | * Manage and deploy SSL/TLS certificates: Makes SSL/TLS encryption possible; which is a protocol for encrypting internet traffic and verifying server identify. This makes your app go from HTTP -> HTTP'''S'''
| |
| − | * There are multiple ways of creating SSL/TLS certificates. However, since we have a domain in Route 53, AWS makes this very easy for us.
| |
| − | :* Go to '''AWS Certificate Manager''' > Request a certificate > Request a public certificate:
| |
| − | ::* Domain names:
| |
| − | ::: sinfrontera.net (Fully qualified domain name)
| |
| − | ::: *.webapp.sinfrontera.net
| |
| − | ::: *.sinfrontera.net
| |
| − | ::* Validation method: DNS validation - recommended
| |
| − | | |
| − | | |
| − | :* Then, go to '''AWS Certificate Manager''' > Certificates:
| |
| − | ::* Click on the Certificate ID we just created, which should appear with a "Pending validation" status
| |
| − | ::* Under "Domains" click on "Create records in Route 53"
| |
| − | ::* Select all the domain/subdomains and click "Create records"
| |
| − | ::* After a while, all of our domain/subdomain should appear with a "Success" status
| |
| − | | |
| − | | |
| − | * After we have created cerificates for our domain/subdomains, our elastic beanstalk will still only work with HTTP, not HTTPS. This is because:
| |
| − | :* Our EC2 rules does not yet allow for HTTPS, only HTTP. So, in our EC2 Security Groups we need to created a HTTPS inbound rule.
| |
| − | :* EC2 intances don't allow HTTPS. So, we need to configure a '''load balancer'''
| |
| − | | |
| − | | |
| − | <br />
| |
| − | | |
| − | ====Configuring a Load Balancer====
| |
| − | https://www.udemy.com/course/deploy-fastapi-fullstack-amazon-cloud-aws/learn/lecture/44339074#overview
| |
| − | | |
| − | We need to configure our Elastic Beanstalk to now work with a Load balancer
| |
| − | * Elasti Beanstalk > Environments > Webapp-fastapi-1-env (Our app env) Configuration > Instance traffic and scaling > edit:
| |
| − | : Before configuring the Load balance we'll see "Auto scalling group: Single instance". The '''Single linstance''' environement doesn't allow HTTPS. So we need to configure a Load balancer:
| |
| − | :: Instances: Min: 1 / Max: 1
| |
| − | :: Apply the changes and wait until the Elastic Beanstalk environment has been updated successfully
| |
| − | | |
| − | * We can now go to EC2 > Load Balancers:
| |
| − | :* You'll see that our Load balancer has been created using 3 AZs. We can modify this to only use 2 so it'll be cheaper. Select the Load Balancer and go to '''Network mapping''' > Edit subnets: Here we can just uncheck one of the AZs
| |
| − | :* Then click on '''Listener and rules''':
| |
| − | :* We need to add a new listener for the '''HTTPS''' protocol (port 443)
| |
| − | :* Target group: select your target type instance
| |
| − | :* Default SSL/TLS server certificate: From ACM and we need to select the certificate we've created for our domain
| |
| − | :* Scroll down to the end and click Add
| |
| − | :* After this, we'll still see that HTTPS is not reachable. This is becuase we're missing the inbound rule for our EC2
| |
| − | :* So we go to '''Security'''. Click the Security group ID and add an inbound rule for HTTPS
| |
| − | | |
| − | | |
| − | <br />
| |
| − | | |
| − | ===React deployment using AWS Amplify===
| |
| − | https://www.udemy.com/course/deploy-fastapi-fullstack-amazon-cloud-aws/learn/lecture/44341044#overview
| |
| − | | |
| − | | |
| − | '''Amplify''' allows us to deploy our React applications efficiently and implement CI/CD for our front end application.
| |
| − | | |
| − | | |
| − | <br />
| |
| − | ====Creating the Amplify app====
| |
| − | | |
| − | '''Amplify > Create a new app:'''
| |
| − | : GitHub > sigin into your GitHub account
| |
| − | : App name: webapp-nextjs-1
| |
| − | : Frontend build command: npm run build
| |
| − | : Build output directory: .next
| |
| − | : Environment variables:
| |
| − | :: NEXT_PUBLIC_API_URL = https://api.webapp.sinfrontera.net
| |
| − | | |
| − | | |
| − | [[File:AWS_Amplify_configuration.png|600px|thumb|center|Amplify new app configuration]]
| |
| − | | |
| − | | |
| − | [[File:AWS_Amplify_configuration-review.png|600px|thumb|center|Amplify new app configuration - Review]]
| |
| − | | |
| − | | |
| − | <br />
| |
| − | ====Configuring a custom domain in our Amplify app====
| |
| − | | |
| − | https://www.udemy.com/course/deploy-fastapi-fullstack-amazon-cloud-aws/learn/lecture/44339080#overview
| |
| − | | |
| − | | |
| − | Amplify > All apps > webapp-nextjs-1:
| |
| − | : Hosting > Custom domains > Add domain: If our domain is in Route 53, it will be available to be added easily. Otherwise, we'll need to complete some configuration on our domain provider.
| |
| − | | |
| − | : Then we just need to configure the subdomain where we want our app to be.
| |
| − | | |
| − | | |
| − | [[File:AWS_Amplify-Custom_domain.png|600px|thumb|center|Amplify - Custom domains]]
| |
| − | | |
| − | | |
| − | <br />
| |
| − | | |
| − | ====Configuring the API_URL environment variable in our backend (fastapi) app====
| |
| − | | |
| − | Elastic Beanstalk > Environments > webapp-fastapi-1-env > Configuration > Edit - Environment properties:
| |
| − | : API_URL = https://webapp.sinfrontera.net
| |
| − | | |
| − | | |
| − | <br />
| |
| − | | |
| − | ==FastAPI - The Complete Course 2023 (Beginner + Advanced)==
| |
| − | This is a Udemy courser: https://www.udemy.com/course/fastapi-the-complete-course/
| |
| − | | |
| − | Source code: https://github.com/codingwithroby/FastAPI-The-Complete-Course
| |
| − | | |
| − | | |
| − | ===Theory===
| |
| − | | |
| − | | |
| − | <br />
| |
| − | ====HTTP request methods====
| |
| − | {|style="width: 20px; height: 20px; margin: 0 auto;"
| |
| − | |
| |
| − | [[File:CRUD_operations_and_HTTP_requests_2.png|x270px|thumb|center|]]
| |
| − | |[[File:CRUD_operations_and_HTTP_requests_3.png|x270px|thumb|center|]]
| |
| − | |}
| |
| − | | |
| − | | |
| − | *'''CRUD''':
| |
| − | | |
| − | :* '''GET''' : Read method: retrieves data
| |
| − | :* '''POST''' : Create method, to submit data. POST can have a body that has additional information that GET does not have.
| |
| − | :* '''PUT''' : Update the entire resource. PUT can also have a body.
| |
| − | :* '''PATCH''' : Update part of the resource
| |
| − | :* '''DELETE''' : Delete the resource
| |
| − | | |
| − | * '''TRACE''' : Performs a message loop-back to the target
| |
| − | * '''OPTIONS''' : Describes communication options to the target
| |
| − | * '''CONNECT''' : Creates a tunnel to the server, based on the target resource
| |
| − | | |
| − | | |
| − | <br />
| |
| − | ====Response status code====
| |
| − | https://www.udemy.com/course/fastapi-the-complete-course/learn/lecture/36994202#overview
| |
| − | | |
| − | <br />
| |
| − | * '''1xx''' : Informational Response: Requestprocessing
| |
| − | | |
| − | * '''2xx''' : Success: Request successfully complete
| |
| − | :* '''200: OK''' : Standard Response for a Successful Request. Commonly used for successful Get requests when data is being returned.
| |
| − | :* '''201''' : The request has been successful, creating a new resource. Used when a POST creates an entity.
| |
| − | :* '''204: No''' : The request has been successful, did not create an entity nor return anything. Commonly used with PUT requests.
| |
| − | | |
| − | * '''3xx''' : Redirection: Further action must be complete
| |
| − | | |
| − | * '''4xx''' : Client Errors: An error was caused by the request from the client
| |
| − | :* '''400: Bad''' : Cannot process request due to client error. Commonly used for invalid request methods.
| |
| − | :* '''401: Unauthorized''' : Client does not have valid authentication for target resource
| |
| − | :* '''404: Not''' : The clients requested resource can not be found
| |
| − | :* '''422: UnprocessableEntity''' : Semantic Errors in Client Request
| |
| − | | |
| − | * '''5xx''' : Server Errors: An error has occurred on the server
| |
| − | :* '''500: Internal Server Error''' : Generic Error Message, when an unexpected issue on the server happened.
| |
| − | | |
| − | | |
| − | <br />
| |
| − | | |
| − | ===Project 1 - A first very basic example===
| |
| − | | |
| − | <br />
| |
| − | '''Creating a FastAPI application:'''
| |
| − | | |
| − | <code>project_1/books.py</code>
| |
| − | <syntaxhighlight lang="python3">
| |
| − | from fastapi import FastAPI, Body
| |
| − | | |
| − | app = FastAPI()
| |
| − | | |
| − | | |
| − | BOOKS = [
| |
| − | {'title': 'Title One', 'author': 'Author One', 'category': 'science'},
| |
| − | {'title': 'Title Two', 'author': 'Author Two', 'category': 'science'},
| |
| − | {'title': 'Title Three', 'author': 'Author Three', 'category': 'history'},
| |
| − | {'title': 'Title Four', 'author': 'Author Four', 'category': 'math'},
| |
| − | {'title': 'Title Five', 'author': 'Author Five', 'category': 'math'},
| |
| − | {'title': 'Title Six', 'author': 'Author Two', 'category': 'math'}
| |
| − | ]
| |
| − | | |
| − | | |
| − | @app.get("/books")
| |
| − | async def read_all_books():
| |
| − | return BOOKS
| |
| − | | |
| − | | |
| − | @app.get("/books/{book_title}") # path parameter
| |
| − | async def read_book(book_title: str):
| |
| − | for book in BOOKS:
| |
| − | if book.get('title').casefold() == book_title.casefold():
| |
| − | return book
| |
| − | | |
| − | | |
| − | @app.get("/books/") # query parameter: http://127.0.0.1:8000/books/?category=science
| |
| − | async def read_category_by_query(category: str):
| |
| − | books_to_return = []
| |
| − | for book in BOOKS:
| |
| − | if book.get('category').casefold() == category.casefold():
| |
| − | books_to_return.append(book)
| |
| − | return books_to_return
| |
| − | | |
| − | | |
| − | @app.get("/books/byauthor/") # Get all books from a specific author using path or query parameters
| |
| − | async def read_books_by_author_path(author: str):
| |
| − | books_to_return = []
| |
| − | for book in BOOKS:
| |
| − | if book.get('author').casefold() == author.casefold():
| |
| − | books_to_return.append(book)
| |
| − | | |
| − | return books_to_return
| |
| − | | |
| − | | |
| − | @app.get("/books/{book_author}/") # Using path parameters AND query parameters
| |
| − | async def read_author_category_by_query(book_author: str, category: str):
| |
| − | books_to_return = []
| |
| − | for book in BOOKS:
| |
| − | if book.get('author').casefold() == book_author.casefold() and \
| |
| − | book.get('category').casefold() == category.casefold():
| |
| − | books_to_return.append(book)
| |
| − | | |
| − | return books_to_return
| |
| − | | |
| − | | |
| − | @app.post("/books/create_book") # This end-point doesn't have any parameter but it has a required «body» where we can pass a new book entry, such as {"title": "Title Seven", "author": "Author One", "category": "history"}
| |
| − | async def create_book(new_book=Body()):
| |
| − | BOOKS.append(new_book)
| |
| − | | |
| − | | |
| − | @app.put("/books/update_book") # As in the case of the POST method, PUT requires a «body» where we can also pass a book. In this case, if we pass a new book whose title matches some of the existing books, it will be updated. For example: {"title": "Title Six", "author": "Author One", "category": "history"}
| |
| − | async def update_book(updated_book=Body()):
| |
| − | for i in range(len(BOOKS)):
| |
| − | if BOOKS[i].get('title').casefold() == updated_book.get('title').casefold():
| |
| − | BOOKS[i] = updated_book
| |
| − | | |
| − | | |
| − | @app.delete("/books/delete_book/{book_title}")
| |
| − | async def delete_book(book_title: str):
| |
| − | for i in range(len(BOOKS)):
| |
| − | if BOOKS[i].get('title').casefold() == book_title.casefold():
| |
| − | BOOKS.pop(i)
| |
| − | break
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | | |
| − | We can run our first app:
| |
| − | <syntaxhighlight lang="bash">
| |
| − | uvicorn books:app --reload
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | | |
| − | http://127.0.0.1:8000/books
| |
| − | | |
| − | FastAPI has an integrated Swagger: http://127.0.0.1:8000/docs
| |
| − | | |
| − | Take a look at this lesson https://www.udemy.com/course/fastapi-the-complete-course/learn/lecture/29025524#overview to see how to try a post request using Swagger.
| |
| − | | |
| − | | |
| − | <br />
| |
| − | | |
| − | ===Project 2 - Data validations and HTTP Exceptions===
| |
| − | | |
| − | <br />
| |
| − | '''Data validations''':
| |
| − | <blockquote>
| |
| − | '''POST/PUT request body data validation:''' https://www.udemy.com/course/fastapi-the-complete-course/learn/lecture/36994096#overview
| |
| − | : '''Pydantics''' is used for data modeling, data parsing and has efficient error handling. '''Pydantics''' is commonly used as a resource for data validation and how to handle data comming to our FastAPI application.
| |
| − | | |
| − | | |
| − | : With '''Pydantics''':
| |
| − | :* We're gonna create a different request model for data validation.
| |
| − | :* We're gonna add Pydantics Field data validation on each variable/element of the request body.
| |
| − | | |
| − | | |
| − | '''Path parameters validation:''' https://www.udemy.com/course/fastapi-the-complete-course/learn/lecture/29025686#overview
| |
| − | : With Pydantic can add data validation for the post or put request body; but not for our path parameters.
| |
| − | : In order to add validationsfor path parameters, we can use the '''<code>Path()</code>''' class from <code>fastapi</code>
| |
| − | | |
| − | | |
| − | '''Query parameters validation:''' https://www.udemy.com/course/fastapi-the-complete-course/learn/lecture/36994198#overview
| |
| − | : This can be done with the '''<code>Query()</code>''' class from <code>fastapi</code>
| |
| − | </blockquote>
| |
| − | | |
| − | | |
| − | '''HTTP Exceptions:''' https://www.udemy.com/course/fastapi-the-complete-course/learn/lecture/36994210#overview
| |
| − | <blockquote>
| |
| − | HTTP exception is something that we have to raise within our method, which will cancel the functionality of our method and return a status code and a message back to the user so the user is able to know what happened with the request.
| |
| − | | |
| − | We're gonna handle HTTP Exceptions with the <code>HTTPException</code> class from from <code>fastapi</code>
| |
| − | | |
| − | | |
| − | '''Explicit status code responses:''' https://www.udemy.com/course/fastapi-the-complete-course/learn/lecture/36994220#overview <br />
| |
| − | We can also add status code responses for a successful API endpoint request. So, so far it will be returned a 200 if the request is a success. Well, a 200 does mean success, but we can go a little bit more detail than just a normal 200 and dictate exactly what status response is returned after each successful.
| |
| − | | |
| − | To do so we're gonna be using <code>from starlette import status</code>. <code>fastAPI</code> is built using <code>Starlet</code>, so <code>Starlet</code> will be installed automatically when you install <code>fastAPI</code>.
| |
| − | </blockquote>
| |
| − | | |
| − | | |
| − | <code>project_2/books2.py</code>
| |
| − | <syntaxhighlight lang="python3">
| |
| − | from typing import Optional
| |
| − | | |
| − | from fastapi import FastAPI, Path, Query, HTTPException
| |
| − | from pydantic import BaseModel, Field
| |
| − | from starlette import status
| |
| − | | |
| − | app = FastAPI()
| |
| − | | |
| − | | |
| − | class Book:
| |
| − | id: int
| |
| − | title: str
| |
| − | author: str
| |
| − | description: str
| |
| − | rating: int
| |
| − | published_date: int
| |
| − | | |
| − | def __init__(self, id, title, author, description, rating, published_date):
| |
| − | self.id = id
| |
| − | self.title = title
| |
| − | self.author = author
| |
| − | self.description = description
| |
| − | self.rating = rating
| |
| − | self.published_date = published_date
| |
| − | | |
| − | | |
| − | class BookRequest(BaseModel): # In project 1 we used Body (from fastapi import Body) as the type of the object that passed in the body of the request. However, using «Body()» we are not able tu add data validations, which can be done with BookRequest(BaseModel)
| |
| − | id: Optional[int] = Field(title='id is not needed')
| |
| − | title: str = Field(min_length=3)
| |
| − | author: str = Field(min_length=1)
| |
| − | description: str = Field(min_length=1, max_length=100)
| |
| − | rating: int = Field(gt=0, lt=6)
| |
| − | published_date: int = Field(gt=1999, lt=2031)
| |
| − | | |
| − | class Config:
| |
| − | schema_extra = { # This adds an example value that will be displayed in our Swagger. Note that there is no id cause we want the id to be autogenerated
| |
| − | 'example': {
| |
| − | 'title': 'A new book',
| |
| − | 'author': 'codingwithroby',
| |
| − | 'description': 'A new description of a book',
| |
| − | 'rating': 5,
| |
| − | 'published_date': 2029
| |
| − | }
| |
| − | }
| |
| − | | |
| − | | |
| − | BOOKS = [
| |
| − | Book(1, 'Computer Science Pro', 'codingwithroby', 'A very nice book!', 5, 2030),
| |
| − | Book(2, 'Be Fast with FastAPI', 'codingwithroby', 'A great book!', 5, 2030),
| |
| − | Book(3, 'Master Endpoints', 'codingwithroby', 'A awesome book!', 5, 2029),
| |
| − | Book(4, 'HP1', 'Author 1', 'Book Description', 2, 2028),
| |
| − | Book(5, 'HP2', 'Author 2', 'Book Description', 3, 2027),
| |
| − | Book(6, 'HP3', 'Author 3', 'Book Description', 1, 2026)
| |
| − | ]
| |
| − | | |
| − | BOOKS
| |
| − | | |
| − | | |
| − | @app.get("/books", status_code=status.HTTP_200_OK) # By using status.HTTP_200_OK, we dictate exactly what status response is returned after each successful request
| |
| − | async def read_all_books():
| |
| − | return BOOKS
| |
| − | | |
| − | @app.get("/books/{book_id}", status_code=status.HTTP_200_OK)
| |
| − | async def read_book(book_id: int = Path(gt=0)): # With Pydantic we've added data validation for the post or put request body; but with haven't added any validation for our path parameters
| |
| − | for book in BOOKS: # Now, to add validations for path parameters, we can use the Path() class
| |
| − | if book.id == book_id:
| |
| − | return book
| |
| − | raise HTTPException(status_code=404, detail='Item not found') # By adding HTTP Exceptions a status code and message is sent back to the user so the user is able to know what happened with the request
| |
| − | | |
| − | | |
| − | @app.get("/books/", status_code=status.HTTP_200_OK)
| |
| − | async def read_book_by_rating(book_rating: int = Query(gt=0, lt=6)): # Query parameters validation using Query()
| |
| − | books_to_return = []
| |
| − | for book in BOOKS:
| |
| − | if book.rating == book_rating:
| |
| − | books_to_return.append(book)
| |
| − | return books_to_return
| |
| − | | |
| − | | |
| − | @app.get("/books/publish/", status_code=status.HTTP_200_OK)
| |
| − | async def read_books_by_publish_date(published_date: int = Query(gt=1999, lt=2031)):
| |
| − | books_to_return = []
| |
| − | for book in BOOKS:
| |
| − | if book.published_date == published_date:
| |
| − | books_to_return.append(book)
| |
| − | return books_to_return
| |
| − | | |
| − | | |
| − | @app.post("/create-book", status_code=status.HTTP_201_CREATED)
| |
| − | async def create_book(book_request: BookRequest):
| |
| − | new_book = Book(**book_request.dict()) # Here we are converting the book_request object, which is type BookRequest to a type Book
| |
| − | BOOKS.append(find_book_id(new_book))
| |
| − | | |
| − | | |
| − | def find_book_id(book: Book):
| |
| − | book.id = 1 if len(BOOKS) == 0 else BOOKS[-1].id + 1
| |
| − | return book
| |
| − | | |
| − | | |
| − | @app.put("/books/update_book", status_code=status.HTTP_204_NO_CONTENT) # This is the most common status code for a PUT request. It means, the request was successful but no content is returned to the client.
| |
| − | async def update_book(book: BookRequest):
| |
| − | book_changed = False
| |
| − | for i in range(len(BOOKS)):
| |
| − | if BOOKS[i].id == book.id:
| |
| − | BOOKS[i] = book
| |
| − | book_changed = True
| |
| − | if not book_changed:
| |
| − | raise HTTPException(status_code=404, detail='Item not found')
| |
| − | | |
| − | | |
| − | @app.delete("/books/{book_id}", status_code=status.HTTP_204_NO_CONTENT)
| |
| − | async def delete_book(book_id: int = Path(gt=0)):
| |
| − | book_changed = False
| |
| − | for i in range(len(BOOKS)):
| |
| − | if BOOKS[i].id == book_id:
| |
| − | BOOKS.pop(i)
| |
| − | book_changed = True
| |
| − | break
| |
| − | if not book_changed:
| |
| − | raise HTTPException(status_code=404, detail='Item not found')
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | | |
| − | <br />
| |
| − | | |
| − | ==Build a Modern API with FastAPI and Python==
| |
| − | This is a Udemy courser: https://www.udemy.com/course/draft/4977924/learn/lecture/35148314#overview
| |
| − | | |
| − | | |
| − | During this course we will build a Movie Tracking API with Fast API and Python for tracking movies.
| |
| − | | |
| − | | |
| − | * '''Section 2: Environment Setup'''
| |
| − | : We will install Pycharm, Docker and Docker-compose and Insomnia. If you already have an environment you can skip this section.
| |
| − | : Note: If you're an university student you can apply for a free licence of Pycharm Professional here: https://www.jetbrains.com/community/education/#students
| |
| − | | |
| − | * '''Section 3: Docker Basic'''
| |
| − | : We will learn some basic docker commands that will help us in improving our workflow.
| |
| − | | |
| − | * '''Section 4: MongoDB Basics'''
| |
| − | : We will learn very basic MongoDB commands and we'll execute them inside the docker container and in Pycharm Professional.
| |
| − | | |
| − | * '''Section 5: Web API Project Structure'''
| |
| − | : In this section we'll learn how to structure the project and we will write some basic endpoints with FastAPI just to make you more familiar with writing endpoints.
| |
| − | | |
| − | * '''Section 6: Storage Layer'''
| |
| − | : We talk about CRUD and we'll apply the repository pattern to develop and In-Memory repository and a MongoDB repository in order to use them within our application. We will also test the implementations.
| |
| − | | |
| − | * '''Section 7: Movie Tracker API'''
| |
| − | : We will write the actual API for tracking movies using the previous developed components. We'll implement the application's settings module and we'll add pagination to some of the routes. In the end we will write unit tests.
| |
| − | | |
| − | * '''Section 8: Middleware'''
| |
| − | : We'll talk about Fast API middleware and how to write your own custom middleware.
| |
| − | | |
| − | * '''Section 9: Authentication'''
| |
| − | : We'll talk about implementing Basic Authentication and validating JWT tokens.
| |
| − | | |
| − | * '''Section 10: Deployment'''
| |
| − | : We'll containerize the application and we will deploy it on a local microk8s kubernetes cluster. In the end we'll visualise some metrics with Grafana. Having metrics is a good way to observe your applications performance and behaviour and troubleshoot it.
| |
| − | | |
| − | * '''Resources for this lecture at ''' https://www.udemy.com/course/build-a-movie-tracking-api-with-fastapi-and-python/learn/lecture/35148314?start=1#overview
| |
| − | | |
| − | | |
| − | ===Environment setup===
| |
| − | * python3, python3-pip, docker, docker Compose.
| |
| − | | |
| − | * '''IDE''': The instructor is using Pycharm Professional, which is paid but we can install Pycharm community. On Ubuntu:
| |
| − | : <syntaxhighlight lang="bash">
| |
| − | sudo snap install pycharm-community --classic
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | | |
| − | * '''Database''': MongoDB in a Docker container
| |
| − | : '''DB IDE''':
| |
| − | ::* I thinks the instructor is also using PyCharm.
| |
| − | ::* I'll be using the MongoDB for VS Code extension:
| |
| − | ::: https://www.mongodb.com/products/vs-code
| |
| − | ::: https://www.youtube.com/watch?v=MLWlWrRAb4w
| |
| − | ::: https://www.youtube.com/watch?v=1d_G8jr0KJE
| |
| − | | |
| − | | |
| − | * For testing the API we are going to use '''Insomnia''', which is a powerful REST client that allows developers to interact with and test RESTful APIs. It provides a user-friendly interface for making HTTP requests, inspecting responses, and debugging API interactions. The tool is often used for tasks such as sending requests, setting headers, managing authentication, and viewing API documentation.
| |
| − | : To install it on Ubuntu:
| |
| − | : <syntaxhighlight lang="bash">
| |
| − | sudo snap install insomnia
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | | |
| − | <br />
| |
| − | ===Creating our Mongo Docker comtainer using docker-compose===
| |
| − | | |
| − | <code>docker-compose.yaml</code>
| |
| − | <syntaxhighlight lang="bash">
| |
| − | version: '3.1'
| |
| − | | |
| − | services:
| |
| − | mongo:
| |
| − | image: mongo:5.0.14
| |
| − | restart: always
| |
| − | ports:
| |
| − | - "27017:27017"
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | <syntaxhighlight lang="bash">
| |
| − | docker-compose up -d
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | | |
| − | <br />
| |
| − | ===MongoDB basics===
| |
| − | | |
| − | We can start our MongoDB shell by:
| |
| − | <syntaxhighlight lang="bash">
| |
| − | docker exec -it fastapi-rest-api-movie-tracker-mongo-1 mongosh
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | Or we can use our favory DB IDE to manage our MongoDB. I'm using MongoDB for VS Code extension.
| |
| − | | |
| − | | |
| − | Let's see some MongoDB basics:
| |
| − | | |
| − | <code>playground-1.mongodb.js</code>
| |
| − | <syntaxhighlight lang="js">
| |
| − | show('databases') // show databases can be used from mongosh
| |
| − | use('movies') // use movies can be used from mongosh. If the movies collection doesn't exits it will create it
| |
| − | | |
| − | db.movies.insertOne({'title':'My Movie', 'year':2022, watched:false})
| |
| − | | |
| − | db.movies.insertMany([
| |
| − | {
| |
| − | 'title': 'The Shawshank Redemption',
| |
| − | 'year': 1994,
| |
| − | 'watched': false
| |
| − | },
| |
| − | {
| |
| − | 'title': 'The Dark Knkght',
| |
| − | 'year': 2008,
| |
| − | 'watched': false
| |
| − | },
| |
| − | {
| |
| − | 'title': 'Puld Fiction',
| |
| − | 'year': 1994,
| |
| − | 'watched': false
| |
| − | },
| |
| − | {
| |
| − | 'title': 'Fight Club',
| |
| − | 'year': 1999,
| |
| − | 'watched': false
| |
| − | },
| |
| − | {
| |
| − | 'title': 'The Lord of the Rings: The Two Towers',
| |
| − | 'year': 2002,
| |
| − | 'watched': false
| |
| − | },
| |
| − | ])
| |
| − | | |
| − | db.movies.findOne()
| |
| − | | |
| − | // Find all the movies
| |
| − | db.movies.find()
| |
| − | | |
| − | // Filtering by title
| |
| − | db.movies.find({'title': 'Fight Club'})
| |
| − | | |
| − | // Find a movie that has been produced before 2000
| |
| − | db.movies.find({'year': {'$lt': 2000}})
| |
| − | | |
| − | // Filter by id
| |
| − | db.movies.find({'_id':ObjectId('647b582d74a86f2cb913d881')})
| |
| − | | |
| − | // Find all movies but skip the first one and limit the result to only 2 movies
| |
| − | db.movies.find().skip(1).limit(2)
| |
| − | | |
| − | // Sorting
| |
| − | db.movies.find().sort({'year':-1}) // 1: ascending; -1: descending
| |
| − | | |
| − | // Select only some attributes
| |
| − | db.movies.find({}, {'title':1, 'year':1})
| |
| − | db.movies.find({}, {'title':0})
| |
| − | | |
| − | // Delete a document
| |
| − | db.movies.deleteOne({'_id': ObjectId('647b55cdc98722c8abe8ee94')})
| |
| − | db.movies.find()
| |
| − | | |
| − | // Updating
| |
| − | db.movies.updateOne({'_id': ObjectId('647b582d74a86f2cb913d87e')}, {$set: {'watched': true}})
| |
| − | db.movies.updateOne({'_id': ObjectId('647b582d74a86f2cb913d87e')}, {$inc: {'year': -3}})
| |
| − | db.movies.find()
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | | |
| − | | |
| − | <br />
| |