
mkdir react-fastapi-infosite-001code react-fastapi-infosite-001python -m venv envsource env/Scripts/activatepip install fastapi[all] (about 40 seconds)from fastapi import FastAPIapp = FastAPI()@app.get("/")async def hello_world():return {"message": "hello world"}
#!/bin/bashuvicorn main:app --reload --port 3355
./serve.sh


git init -b devenv__pycache__



cd backend./serve.sh
import jsonfrom fastapi import APIRouterrouter = APIRouter(prefix="/skills")@router.get("/")async def get_skills():with open("data/skills.json", "r") as file:skills = json.load(file)return skills
from routers import skillsapp.include_router(skills.router)

from fastapi import APIRouter, HTTPException, status@router.get("/{id}")async def get_skill(id:int):with open("data/skills.json", "r") as file:skills = json.load(file)try:skill = next(skill for skill in skills if skill["id"] == id)return skillexcept:raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="skill not found")





npm inpm run dev
"frontend": "vite --port 3663 --open",
npm run frontendsource env/Scripts/activatecd backend./serve.sh
"backend": "bash ./start-backend.sh",
npm run backend

npm i -D concurrently
"dev": "conc \"npm run backend\" \"npm run frontend\"",
npm run dev

npm i axiosuseEffect(() => {(async () => {const response = await axios.get("http://localhost:3355/skills");const skills = response.data;console.log(11111, skills);})();}, []);

from fastapi.middleware.cors import CORSMiddlewareorigins = ["http://localhost:3664"]app.add_middleware(CORSMiddleware, allow_origins=origins)
app.add_middleware(CORSMiddleware, allow_origins=["*"])
npm run backendnpm run frontendimport axios from "axios";import { useTypedStoreState } from "../store/hooks";import { useEffect, useState } from "react";export const PageWelcome = () => {const { message } = useTypedStoreState((state) => state.mainModel);const [skills, setSkills] = useState([]);useEffect(() => {(async () => {const response = await axios.get("http://localhost:3355/skills");const _skills = response.data;setSkills(_skills);})();}, []);return (<><p>{message}</p><h2 className="mt-3 text-xl">{skills.length} Skills</h2></>);};
<ul>{skills.map(skill => {return (<li key={skill.id}>{skill.name}</li>)})}</ul>

export type Skill = {id: number;idCode: string;name: string;description: string;url: string;}
import { Skill } from "../types";const [skills, setSkills] = useState<Skill[]>([]);


const { message } = useTypedStoreState((state) => state.mainModel);
import { Skill } from "../../types";export interface SkillModel {// stateskills: Skill[];}export const skillModel: SkillModel = {// stateskills: [{idCode: "angular",name: "Angular",url: "https://onespace.pages.dev/techItems?id=36",description:"together with React and Vue.js one of the three most popular JavaScript frameworks",id: 1,},{idCode: "cicd",name: "CI/CD",url: "https://about.gitlab.com/topics/ci-cd",description:"the combined practices of continuous integration (CI) and continuous deployment (CD)",id: 2,},],};
import { createStore } from "easy-peasy";import { mainModel, MainModel } from "./models/mainModel";import { skillModel, SkillModel } from "./models/skillModel";export type StoreModel = {mainModel: MainModel;skillModel: SkillModel;};export const store = createStore<StoreModel>({mainModel,skillModel});
import { useTypedStoreState } from "../store/hooks";export const PageWelcome = () => {const { message } = useTypedStoreState((state) => state.mainModel);const { skills } = useTypedStoreState((state) => state.skillModel);return (<><p>{message}</p><h2 className="mt-3 text-xl">{skills.length} Skills</h2><ul>{skills.map((skill) => {return <li key={skill.id}>{skill.name}</li>;})}</ul></>);};

/* eslint-disable @typescript-eslint/no-explicit-any */import { action, Action, thunk, Thunk } from "easy-peasy";import { Skill } from "../../types";import axios from "axios";export interface SkillModel {// stateskills: Skill[];// actionssetSkills: Action<this, Skill[]>;// thunksloadSkillsThunk: Thunk<this>;}export const skillModel: SkillModel = {// stateskills: [],// actionssetSkills: action((state, skills) => {state.skills = structuredClone(skills);}),// thunksloadSkillsThunk: thunk((actions) => {(async () => {try {const response = await axios.get("http://localhost:3355/skills");if (response.status === 200) {const _skills: Skill[] = response.data;actions.setSkills(_skills);}} catch (e: any) {console.log(`ERROR: ${e.message}`);}})();}),};
import { thunk, Thunk } from "easy-peasy";import { StoreModel } from "../store";export interface MainModel {// statemessage: string;// thunksinitialize: Thunk<this, void, void, StoreModel>;}export const mainModel: MainModel = {// statemessage: "This is the welcome page.",// thunksinitialize: thunk((_, __, { getStoreActions }) => {getStoreActions().skillModel.loadSkillsThunk();}),};
import { Outlet } from "react-router-dom";import { Header } from "./components/Header";import { useTypedStoreActions } from "./store/hooks";import { useEffect } from "react";function App() {const {initialize} = useTypedStoreActions(actions => actions.mainModel);useEffect(() => {initialize();})return (<main className="bg-slate-400 p-4 w-full md:w-[60rem] mt-0 md:mt-6"><Header /><main className="py-4"><Outlet /></main></main>);}export default App;



import axios from "axios";import { Skill } from "../types";export const getSkills = async () => {return new Promise<Skill[]>((resolve, reject) => {(async () => {try {const response = await axios.get("http://localhost:3355/skills");if (response.status === 200) {const _skills: Skill[] = response.data;resolve(_skills);}} catch (e: unknown) {reject(`ERROR: ${(e as Error).message}`);}})();});};
import * as dataModel from '../dataModel';loadSkillsThunk: thunk((actions) => {(async () => {const _skills = await dataModel.getSkills()actions.setSkills(_skills);})();}),
npm i zodimport { z } from "zod";export const SkillSchema = z.object({id: z.number(),idCode: z.string(),name: z.string(),description: z.string(),url: z.string().url(),});export type Skill = z.infer<typeof SkillSchema>;
import axios from "axios";import { Skill, SkillSchema } from "../types";export const getSkills = async () => {return new Promise<Skill[]>((resolve, reject) => {(async () => {try {const response = await axios.get("http://localhost:3355/skills");if (response.status === 200) {const _rawSkills: Skill[] = response.data;const _skills: Skill[] = [];for (const _rawSkill of _rawSkills) {const parseResult = SkillSchema.safeParse(_rawSkill);if (parseResult.success) {const _skill: Skill = {id: _rawSkill.id,idCode: _rawSkill.idCode.trim(),name: _rawSkill.name.trim(),description: _rawSkill.description.trim(),url: _rawSkill.url.trim(),};_skills.push(_skill);} else {let r = "";r += `INVALID SKILL IN IMPORT: ${JSON.stringify(_rawSkill,null,2)}\n`;parseResult.error.errors.forEach((err) => {r += `Error in field "${err.path.join(".")}" - ${err.message}\n`;});console.error(r);}}resolve(_skills);}} catch (e: unknown) {reject(`ERROR: ${(e as Error).message}`);}})();});};
{"idCode": "angular","title": "Angular","url": "https://onespace.pages.dev/techItems?id=36","description": "together with React and Vue.js one of the three most popular JavaScript frameworks","id": 1},

import { z } from "zod";export const RawSkillSchema = z.object({id: z.number(),idCode: z.string(),name: z.string(),description: z.string(),url: z.string().url(),});export const SkillSchema = RawSkillSchema.extend({isOpen: z.boolean()})export type RawSkill = z.infer<typeof RawSkillSchema>;export type Skill = z.infer<typeof SkillSchema>;
const _rawSkills: unknown[] = response.data;const _skills: Skill[] = [];for (const _rawSkill of _rawSkills) {const parseResult = RawSkillSchema.safeParse(_rawSkill);if (parseResult.success) {const { id, idCode, name, description, url } =parseResult.data;const _skill: Skill = {id: id,idCode: idCode.trim(),name: name.trim(),description: description.trim(),url: url.trim(),isOpen: false};_skills.push(_skill);
import { useTypedStoreActions, useTypedStoreState } from "../store/hooks";import { Skill } from "../types";export const PageWelcome = () => {const { skills } = useTypedStoreState((state) => state.skillModel);const { saveSkill } = useTypedStoreActions((actions) => actions.skillModel);const handleToggleSkill = (skill: Skill) => {skill.isOpen = !skill.isOpen;saveSkill(skill);};return (<><h2 className="text-xl mb-3">{skills.length} Skills</h2><ul className="list-disc ml-4">{skills.map((skill) => {return (<li key={skill.id}><pclassName={`cursor-pointer w-fit ${skill.isOpen ? 'font-bold' : ''}`}onClick={() => handleToggleSkill(skill)}>{skill.name}</p>{skill.isOpen && (<div className="border border-slate-500 border-1 bg-slate-300 px-2 py-1 mb-2 w-fit rounded-md"><p>{skill.description}</p><p className="text-sm italic">get more info about{" "}<ahref={skill.url}className="underline"target="_blank">{skill.name}</a></p></div>)}</li>);})}</ul></>);};

from fastapi.responses import JSONResponse@router.delete("/{id}")async def delete_skill(id: int):with open("data/skills.json", "r") as file:skills = json.load(file)skill_to_delete = next((skill for skill in skills if skill["id"] == id), None)if not skill_to_delete:raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Skill not found")skills = [skill for skill in skills if skill["id"] != id]with open("data/skills.json", "w") as file:json.dump(skills, file, indent=4)return JSONResponse(content={"deleted": skill_to_delete}, status_code=status.HTTP_200_OK)

import { useTypedStoreActions, useTypedStoreState } from "../store/hooks";import { Skill } from "../types";export const PageWelcome = () => {const { skills } = useTypedStoreState((state) => state.skillModel);const { saveSkill, deleteSkill } = useTypedStoreActions((actions) => actions.skillModel);const handleToggleSkill = (skill: Skill) => {skill.isOpen = !skill.isOpen;saveSkill(skill);};const handleDeleteSkill = (skill: Skill) => {deleteSkill(skill);};return (<><h2 className="text-xl mb-3">{skills.length} Skills</h2><ul className="list-disc ml-4">{skills.map((skill) => {return (<li key={skill.id}><pclassName={`cursor-pointer w-fit ${skill.isOpen ? "font-bold" : ""}`}onClick={() => handleToggleSkill(skill)}>{skill.name}</p>{skill.isOpen && (<div className="border border-slate-500 border-1 bg-slate-300 px-2 py-1 mb-2 w-fit rounded-md"><p>{skill.description}</p><p className="text-sm italic">get more info about{" "}<ahref={skill.url}className="underline"target="_blank">{skill.name}</a></p><div className="flex justify-end"><buttononClick={() =>handleDeleteSkill(skill)}className="">delete</button></div></div>)}</li>);})}</ul></>);};
@layer base {h1 {font-variant: small-caps;}button {@apply bg-gray-400 text-white font-bold py-1 px-2 rounded hover:bg-gray-500;}}
/* eslint-disable @typescript-eslint/no-explicit-any */import { action, Action, thunk, Thunk } from "easy-peasy";import { Skill } from "../../types";import * as dataModel from "../dataModel";import axios from "axios";export interface SkillModel {// stateskills: Skill[];// actionssetSkills: Action<this, Skill[]>;saveSkill: Action<this, Skill>;_deleteSkill: Action<this, Skill>;// thunksloadSkillsThunk: Thunk<this>;deleteSkill: Thunk<this, Skill>;}export const skillModel: SkillModel = {// stateskills: [],// actionssetSkills: action((state, skills) => {state.skills = structuredClone(skills);}),saveSkill: action((state, skill) => {const index = state.skills.findIndex((s) => s.id === skill.id);if (index !== -1) {state.skills[index] = structuredClone(skill);}}),_deleteSkill: action((state, skill) => {const index = state.skills.findIndex((s) => s.id === skill.id);if (index !== -1) {state.skills.splice(index, 1);}}),// thunksloadSkillsThunk: thunk((actions) => {(async () => {const _skills = await dataModel.getSkills();actions.setSkills(_skills);})();}),deleteSkill: thunk((actions, skill) => {actions._deleteSkill(skill);try {(async () => {const response = await axios.delete(`http://localhost:3355/skills/${skill.id}`);if (response.status === 200) {console.log("Skill deleted successfully");} else {console.error("Failed to delete the skill");}})();} catch (error) {console.error("Error deleting skill:", error);}}),};
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"])
