För den som väljer att arbeta med databaser på en lokal server, kan Ubuntu Linux vara en utmärkt lösning. Framför allt om MongoDB:s databas-tjänst (Database-as-a-Service, DBaaS) inte längre är det bästa alternativet, kommer många att vilja sätta upp en egen serverbaserad lösning. Den här guiden går igenom installationen av MongoDB på Ubuntu 22.04 LTS (Jammy), även om versionen också stödjer Ubuntu 20.04 LTS (Focal) för x86_64-arkitektur.

Installationen sker i en Bash-shell, och även om vi går igenom den specifika processen för Ubuntu här, gäller den i stort sett för andra Linux-distributioner med små justeringar.

För att installera MongoDB på Ubuntu, följ dessa steg:

  1. Installera nödvändiga verktyg
    För att säkerställa att de verktyg som behövs för installationen finns på systemet, installera gnupg och curl genom att köra följande kommando:

    bash
    sudo apt-get install gnupg curl

    Detta gör att vi kan importera den offentliga GPG-nyckeln som krävs för installationen.

  2. Importera MongoDB:s GPG-nyckel
    När de nödvändiga verktygen är installerade, kör detta kommando för att hämta och importera GPG-nyckeln:

    bash
    curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | sudo gpg -o /usr/share/keyrings/mongodb-server-7.0.gpg --dearmor
  3. Skapa källfilen för Ubuntu
    För att lägga till MongoDB:s repository till din systemkällista, kör följande kommando:

    bash
    echo "deb [arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list
  4. Uppdatera lokala paketdatabasen
    Efter att källan har lagts till, uppdatera paketlistan med följande kommando:

    bash
    sudo apt-get update
  5. Installera MongoDB
    Installera den senaste stabila versionen av MongoDB genom att köra:

    bash
    sudo apt-get install -y mongodb-org
  6. Starta MongoDB
    För att starta MongoDB-tjänsten, kör:

    bash
    sudo systemctl start mongod

    Om du stöter på ett felmeddelande som säger att mongod.service inte hittades, kör följande kommando för att ladda om systemd och försök sedan starta tjänsten igen:

    bash
    sudo systemctl daemon-reload
    sudo systemctl start mongod
  7. Använd MongoDB Shell
    För att ansluta till MongoDB:s shell, använd kommandot:

    bash
    mongosh

Det är alltid en god idé att undvika att använda de versioner av MongoDB som erbjuds direkt från din Linux-distribution, eftersom dessa inte alltid är uppdaterade till de senaste versionerna.

När du har installerat MongoDB är det också möjligt att ställa in och konfigurera MongoDB Atlas, som är en molnbaserad databas-tjänst. Atlas är ett mycket populärt val för dem som vill ha en fullt hanterad databaslösning utan att behöva ta hand om serverinfrastruktur och underhåll.

För att sätta upp ett konto på MongoDB Atlas, gå till Atlas registreringssidan. Du kan skapa ett konto med din Google-, GitHub- eller e-postadress. När kontot är skapat kommer du att kunna skapa en så kallad "cluster". En M0 cluster är gratis och bör väljas för teständamål. När du väljer en region, välj den som ligger närmast din geografiska plats för att minska latens.

Efter att ha skapat din cluster bör du skapa en databas-användare och konfigurera IP-åtkomst. I den här processen kan du välja att tillåta åtkomst från alla IP-adresser (0.0.0.0/0), men detta rekommenderas inte av säkerhetsskäl. Istället bör du endast tillåta åtkomst från specifika IP-adresser.

För att ansluta till din MongoDB Atlas cluster, gå till din Atlas dashboard och hämta anslutningssträngen som krävs för att koppla samman med din cluster från MongoDB Compass eller din kod.

Det är också viktigt att notera att när du skapar din första cluster och använder Atlas, kan gränssnittet förändras då nya funktioner introduceras. Se alltid till att följa den senaste dokumentationen för att vara säker på att du gör rätt val under installationen och konfigurationen.

Hur man skapar och använder komponenter i React för dynamiska webbapplikationer

I React är en komponent en funktion som returnerar ett UI-element. Komponenter gör det möjligt att dela upp användargränssnittet i mindre, återanvändbara bitar, vilket underlättar utveckling och underhåll av en applikation. Denna modularisering är en grundläggande princip i React och hjälper till att bygga skalbara och dynamiska webbapplikationer. Låt oss titta närmare på hur man skapar och hanterar komponenter i React.

En grundläggande komponent kan definieras på ett enkelt sätt genom att skapa en funktion som returnerar JSX (JavaScript XML), vilket är ett syntaktiskt socker för att skriva HTML-liknande kod i JavaScript. För att skapa en komponent i React, kan vi börja med att skapa en fil, till exempel Header.jsx, och skriva koden för komponenten:

jsx
const Header = () => {
return ( <h1 className="text-center text-purple-500 border-b-2 border-yellow-500"> Header </h1> ); }; export default Header;

Denna komponent är en enkel rubrik (<h1>-tagg) med grundläggande styling via Tailwind CSS. Här används klasser för att centrera texten, sätta textfärgen till lila och ge rubriken en gul kantlinje. När en komponent har skapats, importeras den till huvudapplikationen för att användas. I vårt fall kommer vi att importera Header-komponenten till App.jsx och inkludera den på sidan:

jsx
import Header from "./components/Header";
export default function App() { return ( <div> <Header /> </div> ); }

Efter att ha implementerat denna kod och startat applikationen kommer vi att se en enkel webbsida med en rubrik. Detta är en första introduktion till hur komponenter kan användas för att bygga webbapplikationer med React.

För att skapa mer dynamiska komponenter behöver vi kunna hantera data som skickas till komponenterna. I det här fallet kommer vi att skapa en ny komponent för att visa en lista med bilar. Vi skapar en Card.jsx-komponent som tar emot data om en bil och presenterar den i ett specifikt format:

jsx
const Card = ({ car: { name, year, model, price } }) => { return ( <div className="card"> <h2>{name}</h2> <p>{year} - {model}</p> <p>${price}</p> </div> ); }; export default Card;

I denna komponent använder vi en funktionell komponent och mottar en prop (eller egenskap) som heter car. Genom objektdestrukturering tar vi specifika attribut från objektet car som name, year, model och price. Denna komponent gör det möjligt att skapa kort för varje bil med relevant information.

För att integrera denna nya Card-komponent i vår huvudapplikation, uppdaterar vi App.jsx-filen:

jsx
import { useState } from "react";
import Card from "./components/Card";
export default function App() { const data = [ { id: 1, name: "Fiat", year: 2023, model: "Panda", price: 12000 },
{ id: 2, name: "Peugeot", year: 2018, model: "308", price: 16000 },
{
id: 3, name: "Ford", year: 2022, model: "Mustang", price: 25000 }, // fler bilar ]; const [budget, setBudget] = useState(20000); return ( <div> <h1>Your budget is {budget}</h1> {data.map((el) => (
<Card key={el.id} car={el} />
))}
</div> ); }

Här använder vi map()-metoden för att iterera över data-arrayen och skapa en Card-komponent för varje objekt (bil). Varje bil skickas som en prop till Card-komponenten och visas i användargränssnittet. Vi använder också en enkel state-hantering för att visa och uppdatera användarens budget.

En viktig aspekt att förstå är att React-komponenter ofta tar emot data via "props". Props fungerar som ingångsparametrar för funktionerna, vilket gör det möjligt att dynamiskt ändra innehållet i komponenterna beroende på vilken data de mottar. En annan viktig aspekt är att när vi arbetar med listor av objekt (som bilobjekten i vårt fall) måste vi ge varje komponent ett unikt key-värde. Detta gör att React kan spåra och uppdatera varje komponent effektivt vid förändringar.

När vi bygger en applikation med flera komponenter blir det nödvändigt att hantera stat och händelser. I exemplet ovan använde vi useState för att hantera en enkel användarbudget. State är en fundamental del av React, och det gör det möjligt att skapa interaktiva komponenter där användaren kan påverka applikationens innehåll genom att ändra state-värden.

För att ytterligare göra våra komponenter dynamiska och interaktiva, kan vi lägga till event-hanterare. Till exempel kan vi lägga till ett input-fält som gör det möjligt för användaren att ändra sin budget. När budgeten ändras, uppdateras applikationen dynamiskt och komponenterna renderas om med det nya värdet.

För att sammanfatta, Reacts kraft ligger i möjligheten att skapa små, återanvändbara komponenter som kan ta emot data genom props och hantera interaktivitet genom state och event-hanterare. Detta gör att vi kan bygga komplexa webbapplikationer på ett strukturerat och modulärt sätt, där varje del av applikationen ansvarar för en specifik funktion eller del av gränssnittet.

Det är också viktigt att förstå hur React hanterar rendering och uppdatering av komponenter. React använder ett virtuellt DOM för att effektivt uppdatera användargränssnittet när state eller props förändras, vilket förbättrar prestanda och minimerar onödiga renderingar. Att förstå hur data flödar genom komponenterna och hur React hanterar rendering kan hjälpa utvecklare att skapa mer effektiva och optimerade applikationer.

Hur implementerar man användarautentisering med JWT i React och FastAPI?

Autentisering i moderna webbapplikationer är en grundläggande funktion för att säkerställa att användare har rätt att få tillgång till specifika resurser och data. En av de mest använda teknologierna för att hantera användarautentisering är JSON Web Tokens (JWT), särskilt i samband med Single Page Applications (SPA) där användardata och sessioner måste hanteras på ett effektivt sätt. När man arbetar med React och FastAPI kan denna process uppnås genom att skapa en flexibel lösning som både hanterar användarautentisering och bevarar användarens inloggningsstatus mellan sidladdningar.

Den största delen av logiken för att bevara användarens autentisering finns i useEffect-hooken i React-komponenten. Först hämtas JWT-token från localStorage, och om tokenet finns används det för att hämta användardata från /me-routen. Om användarnamnet finns, sätts det i kontexten och användaren är inloggad. Om inte, rensas localStorage och ett meddelande om att tokenen har löpt ut visas. Här är ett grundläggande exempel på hur en sådan lösning kan implementeras:

I login()-funktionen modifieras den för att hantera localStorage. När en användare loggar in via API:et och får tillbaka en JWT-token, lagras denna token i localStorage så att användarens status bevaras även om sidan laddas om. Denna funktion ser ut så här:

javascript
const login = async (username, password) => {
setJwt(null); const response = await fetch('http://127.0.0.1:8000/users/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) }); if (response.ok) { const data = await response.json(); setJwt(data.token);
localStorage.setItem('jwt', data.token);
setUser({ username }); setMessage(`Login successful: token ${data.token.slice(0, 10)}..., user ${username}`); } else { const data = await response.json(); setMessage('Login failed: ' + data.detail); setUser({ username: null }); } };

En annan viktig aspekt är att implementera en logout()-funktion som rensar localStorage när användaren loggar ut. Det säkerställer att användarens session avslutas korrekt och att den lagrade JWT-token tas bort.

javascript
const logout = () => {
setUser(null); setJwt(''); localStorage.removeItem('jwt'); setMessage('Logout successful'); };

När användaren är inloggad, används JWT-token för att hämta användardata från en annan API-route, till exempel /users/list. Här är en komponent som visar hur användarlistan hämtas och visas:

javascript
const Users = () => { const { jwt, logout } = useAuth(); const [users, setUsers] = useState(null); const [error, setError] = useState(null); useEffect(() => {
const fetchUsers = async () => {
const response = await fetch('http://127.0.0.1:8000/users/list', { headers: { Authorization: `Bearer ${jwt}` } }); const data = await response.json(); if (!response.ok) { setError(data.detail); } setUsers(data.users); }; if (jwt) { fetchUsers(); } }, [jwt]);
if (!jwt) return <p>Please log in to see all the users</p>;
return ( <div> {users ? ( <div> <h2>The list of users</h2> {users.map((user) => ( <p key={user.username}>{user.username}</p> ))}
<button onClick={logout}>Logout</button>
</div> ) : ( <p>{error}</p> )} </div> ); };

Denna lösning gör att användarens inloggning bevaras även efter att sidan laddas om eller efter att webbläsartabben stängs och öppnas igen. Detta kan testas genom att logga in och sedan uppdatera sidan eller öppna en ny flik i webbläsaren. JWT-token lagras i localStorage, vilket gör att applikationen kan återställa användarens autentiseringstillstånd vid behov.

Det är också viktigt att förstå de olika alternativen för autentisering. Firebase och Supabase är populära tjänster som kan användas för att hantera användare och autentisera dem, medan mer nischade alternativ som Clerk och Kinde är särskilt anpassade för ekosystem som React/Next.js/Remix.js. Auth0 och Cognito är industristandardlösningar som erbjuder robusta autentiseringstjänster. Även om dessa tjänster ofta erbjuder gratisnivåer, kan kostnaderna snabbt öka när applikationen växer, och det kan vara svårt att byta till en annan tjänst om behovet uppstår.

En annan metod för att lagra JWT är att använda cookies istället för localStorage. Cookies har vissa säkerhetsfördelar, särskilt när det gäller skydd mot XSS-attacker, men de är också mer utsatta för CSRF-attacker om de inte hanteras korrekt. Beroende på applikationens behov kan det vara värt att överväga en sådan lösning.

Slutligen, när man bygger autentisering och auktorisering, är det viktigt att tänka på vilka system som används för att hämta användardata. Det är inte alltid praktiskt att använda externa tjänster för detta, och det är ofta bättre att skapa en egen lösning som är både flexibel och säker. Genom att noggrant överväga hur autentisering och auktorisering implementeras, kan man bygga en skalbar och säker applikation.

Hur man skapar autentisering och användarhantering i FastAPI med Beanie

Autentisering och användarhantering är centrala komponenter i utvecklingen av moderna webbtjänster. När man bygger en backend med FastAPI och Beanie för MongoDB, är det viktigt att korrekt hantera användardata som registrering, inloggning och verifiering. Här kommer vi att undersöka hur dessa funktioner implementeras med hjälp av JWT för autentisering, samt hur man sätter upp en APIRouter för användare.

Autentisering i FastAPI kan göras på ett strukturerat sätt med hjälp av en särskild hanterareklass, som kapslar in logiken för att generera och verifiera tokens. Denna klass, AuthHandler, hanterar alla viktiga aspekter av autentisering, från lösenordshashning till skapandet och dekodningen av JWT-tokens. JWT (JSON Web Tokens) är en säker metod för att överföra användarinformation i en webbtjänst.

I autentiseringsfilen (authentication.py) kan vi definiera de funktioner som är nödvändiga för att säkerställa en korrekt autentisering:

python
import datetime
import jwt from fastapi import HTTPException, Security from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from passlib.context import CryptContext class AuthHandler: security = HTTPBearer() pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") secret = "FARMSTACKsecretString" def get_password_hash(self, password): return self.pwd_context.hash(password) def verify_password(self, plain_password, hashed_password): return self.pwd_context.verify(plain_password, hashed_password) def encode_token(self, user_id, username): payload = { "exp": datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(minutes=30), "iat": datetime.datetime.now(datetime.timezone.utc), "sub": {"user_id": user_id, "username": username}, } return jwt.encode(payload, self.secret, algorithm="HS256") def decode_token(self, token): try: payload = jwt.decode(token, self.secret, algorithms=["HS256"]) return payload["sub"] except jwt.ExpiredSignatureError: raise HTTPException(status_code=401, detail="Signature has expired") except jwt.InvalidTokenError: raise HTTPException(status_code=401, detail="Invalid token") def auth_wrapper(self, auth: HTTPAuthorizationCredentials = Security(security)): return self.decode_token(auth.credentials)

Denna AuthHandler-klass innehåller funktioner för att generera hashade lösenord, skapa och verifiera tokens, samt dekryptera inkommande autentiseringstokens för att få åtkomst till användarens ID och användarnamn.

När autentiseringen är på plats, är nästa steg att sätta upp användarrutter i en routerfil. Vi kan skapa en user.py-fil som hanterar registrering, inloggning och verifiering av användare via API-rutter. Här är ett exempel på hur man skapar en sådan router i FastAPI:

python
from fastapi import APIRouter, Body, Depends, HTTPException from fastapi.responses import JSONResponse from authentication import AuthHandler from models import CurrentUser, LoginUser, RegisterUser, User auth_handler = AuthHandler() router = APIRouter() @router.post("/register", response_description="Register user", response_model=CurrentUser)
async def register(newUser: RegisterUser = Body(...), response_model=User):
newUser.password = auth_handler.get_password_hash(newUser.password) query = {
"$or": [{"username": newUser.username}, {"email": newUser.email}]} existing_user = await User.find_one(query) if existing_user is not None: raise HTTPException(status_code=409, detail=f"{newUser.username} or {newUser.email} already exists") user = await User(**newUser.model_dump()).save() return user @router.post("/login", response_description="Login user and return token")
async def login(loginUser: LoginUser = Body(...)) -> str:
user =
await User.find_one(User.username == loginUser.username) if user and auth_handler.verify_password(loginUser.password, user.password): token = auth_handler.encode_token(str(user.id), user.username) response = JSONResponse(content={"token": token, "username": user.username}) return response else: raise HTTPException(status_code=401, detail="Invalid username or password") @router.get("/me", response_description="Logged in user data", response_model=CurrentUser) async def me(user_data=Depends(auth_handler.auth_wrapper)): currentUser = await User.get(user_data["user_id"]) return currentUser

Den här user.py-routern definierar tre huvudsakliga rutter: registrering, inloggning och verifiering av användaren. I registreringsrutan kontrolleras om användarnamnet eller e-posten redan finns i databasen, och om så är fallet returneras ett fel. Vid inloggning jämförs användarnamn och lösenord, och om de är korrekta genereras en JWT-token som sedan skickas tillbaka till användaren.

För att autentisering ska fungera ordentligt är det viktigt att förstå hur JWT-tokens används för att identifiera och autentisera användare. När en användare loggar in, får de en token som de sedan kan använda för att bevisa sin identitet vid framtida förfrågningar. Det är avgörande att denna token hanteras på ett säkert sätt, och att systemet kan verifiera att en användare är legitim genom att avkoda token.

Det är också viktigt att förstå att även om användaren får en token vid inloggning, innebär det inte att de är helt "autentiserade" för alla operationer i systemet. Tokens bör ha en utgångstid och kan även återkallas om det finns misstankar om obehörig användning. Säkerheten bör alltid vara högsta prioritet när det gäller att hantera användardata och autentisering.

När man bygger sådana lösningar är det också bra att tänka på andra aspekter som säkerhet, t.ex. att hantera lösenord på ett korrekt sätt genom att använda starka hash-algoritmer som bcrypt och att se till att alla kommunikativa kanaler mellan klient och server är krypterade, t.ex. via HTTPS. Det är också värt att överväga olika sätt att hantera användarsessioner och implementera funktioner som lösenordåterställning och tvåfaktorsautentisering för ytterligare säkerhet.

Hur integrera tredjepartstjänster med FastAPI och Beanie för att bygga en effektiv applikation

När man utvecklar moderna webapplikationer är det ofta nödvändigt att integrera externa tjänster för att förbättra funktionaliteten. Ett exempel på detta är att kombinera FastAPI, Beanie och tredjepartstjänster som OpenAI för att skapa en responsiv och kraftfull applikation. I detta avsnitt utforskar vi hur man kan använda FastAPI och Beanie för att hantera bakgrundsprocesser, uppdatera bilinformation i en databas och skicka automatiserade e-postmeddelanden.

En grundläggande funktion som ofta efterfrågas är att hantera och uppdatera data i en databas baserat på användarens inmatning. Ett exempel på detta är att lägga till en ny bil i en databas och sedan automatiskt generera en beskrivning av bilen med hjälp av OpenAI:s GPT-modell. När användaren laddar upp en bild på bilen och anger dess egenskaper, kan applikationen snabbt skapa en beskrivning och andra relaterade data som fördelar och nackdelar.

I koden som presenteras här används Cloudinary för att ladda upp bilder och hämta en URL som lagras i databasen. Denna funktionalitet sker parallellt med en bakgrundsprocess som anropar OpenAI:s API för att generera en beskrivning av bilen. En fördel med denna metod är att den inte blockerar användarens interaktion med applikationen, eftersom uppdateringen av bilens data sker i bakgrunden och inte påverkar användarens upplevelse direkt.

En viktig aspekt att tänka på här är att de genererade beskrivningarna, fördelarna och nackdelarna som skapas av OpenAI:s modell kanske inte alltid är korrekta eller relevanta. Därför bör man implementera någon form av validering eller godkännandeprocess innan dessa data publiceras. Detta kan till exempel innebära att en människa måste godkänna beskrivningen innan den blir offentlig, eller att ytterligare API-anrop används för att säkerställa kvaliteten på den genererade texten.

Vidare är det möjligt att kombinera denna funktionalitet med e-postmeddelanden för att informera användarna om nya bilar som lagts till i systemet. Tjänster som Resend och SendGrid gör det möjligt att snabbt integrera e-postfunktioner i applikationer. Här används Resend för att skicka e-post med bilens information till registrerade användare. E-postmeddelandet innehåller bilens beskrivning samt fördelar och nackdelar, vilket gör det till en viktig funktion för att hålla användarna uppdaterade om nya produkter i systemet.

För att implementera denna e-postfunktionalitet används en enkel process där e-postens innehåll genereras dynamiskt med hjälp av Python och Resend API:et. För att skicka ett e-postmeddelande används en enkel Python-funktion som definierar avsändare, mottagare, ämne och HTML-innehåll. Den här lösningen är lätt att implementera och kan snabbt utökas för att stödja mer avancerade e-postfunktioner som mallar eller anpassade e-postmeddelanden beroende på användarens preferenser.

Det är också viktigt att överväga hur man hanterar olika typer av scenarier i applikationen. Vad händer om en användare försöker lägga till en bil med en ogiltig modell eller om OpenAI:s svar är ofullständigt eller inte relevant? I sådana fall bör det finnas robusta felhanteringsmekanismer och återkoppling till användaren för att säkerställa en smidig upplevelse.

För att uppnå detta används beanie, en ODM (Object-Document Mapper) för MongoDB som underlättar interaktionen med databasen. Genom att använda beanie kan vi enkelt definiera datamodeller och utföra CRUD-operationer, vilket gör det enkelt att både skapa och uppdatera bilens data i databasen.

Genom att integrera tredjepartstjänster som OpenAI och Resend, samtidigt som man använder kraftfulla Python-ramverk som FastAPI och Beanie, kan man bygga effektiva och skalbara applikationer som tillgodoser moderna krav på funktionalitet och användarupplevelse. Genom att använda bakgrundsjobb och asynkrona funktioner som FastAPI erbjuder, kan man bygga applikationer som både är responsiva och kan hantera långsamma och tunga operationer utan att påverka användarens upplevelse.

En annan aspekt att ta i beaktande är säkerheten vid hantering av användardata och tredjeparts-API:er. Det är avgörande att säkerställa att känslig användarinformation som e-postadresser hanteras korrekt och att autentisering och auktorisering implementeras på ett sätt som skyddar användarnas integritet.

I sammanhanget av denna applikation är det också viktigt att tänka på hur man kan skala systemet för att hantera fler användare och större datamängder. Genom att använda en skalbar databas som MongoDB och parallella bakgrundsprocesser, kan man säkerställa att applikationen klarar av att hantera hög trafik och stora datamängder utan att prestanda påverkas negativt.