diff --git a/.github/workflows/astro.yml b/.github/workflows/astro.yml deleted file mode 100644 index 2ee0e27..0000000 --- a/.github/workflows/astro.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Deploy Astro site to Pages - -on: - push: - branches: ["master"] # adjust if needed - workflow_dispatch: - -permissions: - contents: read - pages: write - id-token: write - -jobs: - build: - name: Build - runs-on: ubuntu-latest - steps: - - name: Checkout your repository using git - uses: actions/checkout@v4 - - - name: Install, build, and upload - uses: withastro/action@v3 - with: - package-manager: bun@latest - - deploy: - name: Deploy to GitHub Pages - needs: build - runs-on: ubuntu-latest - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.github/workflows/nextjs.yml b/.github/workflows/nextjs.yml new file mode 100644 index 0000000..5ff5940 --- /dev/null +++ b/.github/workflows/nextjs.yml @@ -0,0 +1,75 @@ +name: Deploy Next.js site to Pages + +on: + push: + branches: ["v1"] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Detect package manager + id: detect-package-manager + run: | + if [ -f "${{ github.workspace }}/yarn.lock" ]; then + echo "manager=yarn" >> $GITHUB_OUTPUT + echo "command=install" >> $GITHUB_OUTPUT + echo "runner=yarn" >> $GITHUB_OUTPUT + exit 0 + elif [ -f "${{ github.workspace }}/package.json" ]; then + echo "manager=npm" >> $GITHUB_OUTPUT + echo "command=ci" >> $GITHUB_OUTPUT + echo "runner=npx --no-install" >> $GITHUB_OUTPUT + exit 0 + else + echo "Unable to determine package manager" + exit 1 + fi + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: ${{ steps.detect-package-manager.outputs.manager }} + - name: Setup Pages + uses: actions/configure-pages@v5 + with: + static_site_generator: next + - name: Restore cache + uses: actions/cache@v4 + with: + path: | + .next/cache + key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }} + restore-keys: | + ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}- + - name: Install dependencies + run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }} + - name: Build with Next.js + run: ${{ steps.detect-package-manager.outputs.runner }} npm run export + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: ./out + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 16d54bb..eb32e1d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,36 @@ -# build output -dist/ -# generated types -.astro/ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies -node_modules/ +/node_modules +/.pnp +.pnp.js -# logs +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug npm-debug.log* yarn-debug.log* yarn-error.log* -pnpm-debug.log* - -# environment variables -.env -.env.production +# local env files +.env*.local -# macOS-specific files -.DS_Store +# vercel +.vercel -# jetbrains setting folder -.idea/ +# typescript +*.tsbuildinfo +next-env.d.ts +.contentlayer \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 26d70b4..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "recommendations": ["astro-build.astro-vscode", "unifiedjs.vscode-mdx"], - "unwantedRecommendations": [] -} diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 230708d..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "command": "./node_modules/.bin/astro dev", - "name": "Development server", - "request": "launch", - "type": "node-terminal" - } - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 4d360cb..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "typescript.tsdk": "node_modules/typescript/lib" -} diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz new file mode 100644 index 0000000..0d6da18 Binary files /dev/null and b/.yarn/install-state.gz differ diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 0000000..3186f3f --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/README.md b/README.md index 758716e..589bbbf 100644 --- a/README.md +++ b/README.md @@ -1,68 +1,7 @@ -# Astro Starter Kit: Blog +# Next.js + Contentlayer -```sh -npm create astro@latest -- --template blog -``` +A template with Next.js 13 app dir, Contentlayer, Tailwind CSS and dark mode. -[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/blog) -[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/blog) -[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/blog/devcontainer.json) +https://next-contentlayer.vercel.app -> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! -![blog](https://github.com/withastro/astro/assets/2244813/ff10799f-a816-4703-b967-c78997e8323d) - -Features: - -- ✅ Minimal styling (make it your own!) -- ✅ 100/100 Lighthouse performance -- ✅ SEO-friendly with canonical URLs and OpenGraph data -- ✅ Sitemap support -- ✅ RSS Feed support -- ✅ Markdown & MDX support - -## 🚀 Project Structure - -Inside of your Astro project, you'll see the following folders and files: - -```text -├── public/ -├── src/ -│   ├── components/ -│   ├── content/ -│   ├── layouts/ -│   └── pages/ -├── astro.config.mjs -├── README.md -├── package.json -└── tsconfig.json -``` - -Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. - -There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components. - -The `src/content/` directory contains "collections" of related Markdown and MDX documents. Use `getCollection()` to retrieve posts from `src/content/blog/`, and type-check your frontmatter using an optional schema. See [Astro's Content Collections docs](https://docs.astro.build/en/guides/content-collections/) to learn more. - -Any static assets, like images, can be placed in the `public/` directory. - -## 🧞 Commands - -All commands are run from the root of the project, from a terminal: - -| Command | Action | -| :------------------------ | :----------------------------------------------- | -| `npm install` | Installs dependencies | -| `npm run dev` | Starts local dev server at `localhost:4321` | -| `npm run build` | Build your production site to `./dist/` | -| `npm run preview` | Preview your build locally, before deploying | -| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | -| `npm run astro -- --help` | Get help using the Astro CLI | - -## 👀 Want to learn more? - -Check out [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). - -## Credit - -This theme is based off of the lovely [Bear Blog](https://github.com/HermanMartinus/bearblog/). diff --git a/app/[...slug]/page.tsx b/app/[...slug]/page.tsx new file mode 100644 index 0000000..44db94d --- /dev/null +++ b/app/[...slug]/page.tsx @@ -0,0 +1,81 @@ +import { notFound } from "next/navigation"; +import type { Metadata } from "next"; +import { allPages } from "contentlayer/generated"; + +import { Mdx } from "@/components/mdx-components"; +import SocialList from "@/components/social-list"; + +interface PageProps { + params: { + slug: string[]; + }; +} + +async function getPageFromParams(params: PageProps["params"]) { + const slug = params?.slug?.join("/"); + const page = allPages.find((page) => page.slugAsParams === slug); + + if (!page) { + null; + } + + return page; +} + +export async function generateMetadata({ + params, +}: PageProps): Promise { + const page = await getPageFromParams(params); + + if (!page) { + return {}; + } + + return { + title: page.title, + description: page.description, + }; +} + +export async function generateStaticParams(): Promise { + return allPages.map((page) => ({ + slug: page.slugAsParams.split("/"), + })); +} + +export default async function PagePage({ params }: PageProps) { + const page = await getPageFromParams(params); + + if (!page) { + notFound(); + } + + return ( +
+
+
+

+ {page.title} +

+ {page.description && ( +

+ {page.description} +

+ )} +
+
+
+ +
+
+
+
+ +
+

+ © {new Date().getFullYear()} +

+
+
+ ); +} diff --git a/app/favicon.ico b/app/favicon.ico new file mode 100644 index 0000000..5d3e391 Binary files /dev/null and b/app/favicon.ico differ diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..0c55709 --- /dev/null +++ b/app/globals.css @@ -0,0 +1,74 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 243 39% 99%; + --foreground: 243 73% 1%; + --muted: 213 6% 88%; + --muted-foreground: 213 5% 29%; + --popover: 243 39% 98%; + --popover-foreground: 0 0% 0%; + --card: 243 39% 98%; + --card-foreground: 0 0% 0%; + --border: 243 7% 92%; + --input: 243 7% 92%; + --primary: 243 68% 48%; + --primary-foreground: 0 0% 100%; + --secondary: 213 68% 48%; + --secondary-foreground: 0 0% 100%; + --accent: 273 68% 48%; + --accent-foreground: 0 0% 100%; + --destructive: 20 94% 35%; + --destructive-foreground: 0 0% 100%; + --ring: 243 68% 48%; + --radius: 0.5rem; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + } + .dark { + --background: 243 45% 3%; + --foreground: 243 20% 99%; + --muted: 213 6% 12%; + --muted-foreground: 213 5% 71%; + --popover: 243 45% 4%; + --popover-foreground: 0 0% 100%; + --card: 243 45% 4%; + --card-foreground: 0 0% 100%; + --border: 243 7% 12%; + --input: 243 7% 12%; + --primary: 243 68% 48%; + --primary-foreground: 0 0% 100%; + --secondary: 213 68% 48%; + --secondary-foreground: 0 0% 100%; + --accent: 273 68% 48%; + --accent-foreground: 0 0% 100%; + --destructive: 20 94% 60%; + --destructive-foreground: 0 0% 0%; + --ring: 243 68% 48%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } + @font-face { + font-family: 'Belgrano'; + font-style: normal; + font-weight: 400; + src: url('/header.ttf') format('ttf') + } +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..ef64114 --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,60 @@ +import type { ReactNode } from "react"; +import type { Metadata } from "next"; +import config from "@/lib/config"; +import Navigation from "@/components/navigation"; +import { ThemeProvider } from "@/components/theme-provider"; +import "./globals.css"; +import localFont from "next/font/local"; + +const body = localFont({ src: "../assets/fonts/body.ttf" }); + +export const metadata: Metadata = { + title: { + default: config.site_title, + template: `%s | ${config.site_title}`, + }, + description: config.site_description, + openGraph: { + type: "website", + title: { + default: config.site_title, + template: `%s | ${config.site_title}`, + }, + description: config.site_description, + images: "/og_image.png", + url: config.base_url, + }, + metadataBase: new URL(config.base_url), + keywords: config.site_keywords, + authors: [{ name: config.github_account }], + twitter: { + card: "summary_large_image", + site: config.twitter_account, + title: { + default: config.site_title, + template: `%s | ${config.site_title}`, + }, + description: config.site_description, + images: "/og_image.png", + }, +}; +export default function RootLayout({ children }: { children: ReactNode }) { + return ( + + + +
+
+ +
+
+ {children} +
+
+
+ + + ); +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..9d5b6ea --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,22 @@ +import { FloatingGlass } from "@/components/glass"; +import { HeroSection } from "@/components/home/hero"; +import MobilePage from "@/components/home/mobile"; +import { Separator } from "@/components/ui/separator"; +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Home", +}; + +export default function Page() { + return ( +
+ + +
+ + +
+
+ ); +} diff --git a/app/posts/[...slug]/page.tsx b/app/posts/[...slug]/page.tsx new file mode 100644 index 0000000..4e550c5 --- /dev/null +++ b/app/posts/[...slug]/page.tsx @@ -0,0 +1,92 @@ +import { notFound } from "next/navigation"; +import { allPosts } from "contentlayer/generated"; + +import type { Metadata } from "next"; +import { Mdx } from "@/components/mdx-components"; +import SocialList from "@/components/social-list"; + +interface PostProps { + params: { + slug: string[]; + }; +} + +async function getPostFromParams(params: PostProps["params"]) { + const slug = params?.slug?.join("/"); + const post = allPosts.find((post) => post.slugAsParams === slug); + + if (!post) { + null; + } + + return post; +} + +export async function generateMetadata({ + params, +}: PostProps): Promise { + const post = await getPostFromParams(params); + + if (!post) { + return {}; + } + + return { + title: post.title, + description: post.description, + openGraph: { + type: "article", + title: post.title, + description: post.description, + }, + twitter: { + card: "summary_large_image", + title: post.title, + description: post.title, + images: "/og_image.png", + }, + }; +} + +export async function generateStaticParams(): Promise { + return allPosts.map((post) => ({ + slug: post.slugAsParams.split("/"), + })); +} + +export default async function PostPage({ params }: PostProps) { + const post = await getPostFromParams(params); + + if (!post) { + notFound(); + } + + return ( +
+
+
+

+ {post.title} +

+ {post.description && ( +

+ {post.description} +

+ )} +
+
+
+ +
+
+
+
+ +
+

+ © {new Date().getFullYear()} +

+
+
+ ); +} diff --git a/app/posts/page.tsx b/app/posts/page.tsx new file mode 100644 index 0000000..e865bb3 --- /dev/null +++ b/app/posts/page.tsx @@ -0,0 +1,46 @@ +import { allPosts } from "@/.contentlayer/generated"; +import Link from "next/link"; +import type { Metadata } from "next"; + +function formatDate(dateString: string): string { + const date = new Date(dateString); + return date.toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric", + }); +} + +export const metadata: Metadata = { + title: "All Posts", + twitter: { + title: "All Posts", + }, + openGraph: { + title: "All Posts", + }, +}; + +export default function Home() { + return ( +
+
+
    + {allPosts.map((post) => ( +
  • + +

    + {post.title} +

    + + {post.description &&

    {post.description}

    } + +
  • + ))} +
+
+
+ ); +} diff --git a/assets/config.json b/assets/config.json new file mode 100644 index 0000000..6397cc2 --- /dev/null +++ b/assets/config.json @@ -0,0 +1,15 @@ +{ + "base_url": "https://owenstack.github.io", + "site_title": "Owen Stack's Portfolio", + "site_description": "Personal portfolio and blog showcasing my projects and thoughts", + "site_keywords": [ + "Web Development", + "Software Engineering", + "JavaScript", + "React", + "Next.js" + ], + "posts_per_page": 6, + "twitter_account": "@oweneefobi", + "github_account": "owenstack" +} diff --git a/public/fonts/AnonymousPro.ttf b/assets/fonts/body.ttf similarity index 100% rename from public/fonts/AnonymousPro.ttf rename to assets/fonts/body.ttf diff --git a/assets/fonts/wordmark.ttf b/assets/fonts/wordmark.ttf new file mode 100644 index 0000000..15d4ef0 Binary files /dev/null and b/assets/fonts/wordmark.ttf differ diff --git a/assets/icons/github.tsx b/assets/icons/github.tsx new file mode 100644 index 0000000..0394d12 --- /dev/null +++ b/assets/icons/github.tsx @@ -0,0 +1,17 @@ +import * as React from "react"; +import type { SVGProps } from "react"; +const Github = (props: SVGProps) => ( + // biome-ignore lint/a11y/noSvgWithoutTitle: + + + +); +export default Github; diff --git a/src/components/icons/google.tsx b/assets/icons/mail.tsx similarity index 94% rename from src/components/icons/google.tsx rename to assets/icons/mail.tsx index 07e8df6..3917392 100644 --- a/src/components/icons/google.tsx +++ b/assets/icons/mail.tsx @@ -1,6 +1,7 @@ import * as React from "react"; import type { SVGProps } from "react"; const Gmail = (props: SVGProps) => ( + // biome-ignore lint/a11y/noSvgWithoutTitle: ) => ( + // biome-ignore lint/a11y/noSvgWithoutTitle: + + + + + + + + + + +); +export default Telegram; diff --git a/src/components/icons/twitter.tsx b/assets/icons/twitter.tsx similarity index 80% rename from src/components/icons/twitter.tsx rename to assets/icons/twitter.tsx index 2246db0..be7b142 100644 --- a/src/components/icons/twitter.tsx +++ b/assets/icons/twitter.tsx @@ -1,6 +1,7 @@ import * as React from "react"; import type { SVGProps } from "react"; -const XformerlyTwitter = (props: SVGProps) => ( +const X = (props: SVGProps) => ( + // biome-ignore lint/a11y/noSvgWithoutTitle: ) => ( /> ); -export default XformerlyTwitter; +export default X; diff --git a/astro.config.mjs b/astro.config.mjs deleted file mode 100644 index 2c1987d..0000000 --- a/astro.config.mjs +++ /dev/null @@ -1,22 +0,0 @@ -import mdx from "@astrojs/mdx"; -// @ts-check -import { defineConfig } from "astro/config"; - -import sitemap from "@astrojs/sitemap"; - -import tailwind from "@astrojs/tailwind"; - -import react from "@astrojs/react"; - -// https://astro.build/config -export default defineConfig({ - site: "https://owenstack.github.io", - integrations: [ - mdx(), - sitemap(), - tailwind({ - applyBaseStyles: false, - }), - react(), - ], -}); diff --git a/biome.json b/biome.json index beb54d0..3867749 100644 --- a/biome.json +++ b/biome.json @@ -1,18 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/1.9.3/schema.json", - "vcs": { - "enabled": false, - "clientKind": "git", - "useIgnoreFile": false - }, - "files": { - "ignoreUnknown": false, - "ignore": [] - }, - "formatter": { - "enabled": true, - "indentStyle": "tab" - }, + "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json", "organizeImports": { "enabled": true }, @@ -21,10 +8,5 @@ "rules": { "recommended": true } - }, - "javascript": { - "formatter": { - "quoteStyle": "double" - } } } diff --git a/bun.lockb b/bun.lockb index 087585e..2be971c 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/components.json b/components.json index 7f5a3eb..4f7d0bf 100644 --- a/components.json +++ b/components.json @@ -1,20 +1,20 @@ { - "$schema": "https://ui.shadcn.com/schema.json", - "style": "new-york", - "rsc": false, - "tsx": true, - "tailwind": { - "config": "tailwind.config.mjs", - "css": "src/styles/global.css", - "baseColor": "neutral", - "cssVariables": true, - "prefix": "" - }, - "aliases": { - "components": "@/components", - "utils": "@/lib/utils", - "ui": "@/components/ui", - "lib": "@/lib", - "hooks": "@/hooks" - } -} + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + } +} \ No newline at end of file diff --git a/components/analytics.tsx b/components/analytics.tsx new file mode 100644 index 0000000..164e9b7 --- /dev/null +++ b/components/analytics.tsx @@ -0,0 +1,7 @@ +"use client" + +import { Analytics as VercelAnalytics } from "@vercel/analytics/react" + +export function Analytics() { + return +} diff --git a/components/burger.tsx b/components/burger.tsx new file mode 100644 index 0000000..fcf8581 --- /dev/null +++ b/components/burger.tsx @@ -0,0 +1,71 @@ +export interface BurgerProps { + active: boolean; + onClick: () => void; +} + +export default function Burger({ active, onClick }: BurgerProps) { + return ( +
{ + if (e.key === "Enter" || e.key === " ") { + onClick(); + } + }} + role="button" + tabIndex={0} + > +
+
+
+ +
+ ); +} diff --git a/components/glass.tsx b/components/glass.tsx new file mode 100644 index 0000000..edd0208 --- /dev/null +++ b/components/glass.tsx @@ -0,0 +1,60 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; + +interface FloatingGlassProps { + width: number; + height: number; +} + +export function FloatingGlass({ width, height }: FloatingGlassProps) { + const [position, setPosition] = useState({ x: 0, y: 0 }); + const [velocity, setVelocity] = useState({ x: 2, y: 2 }); + const glassRef = useRef(null); + const containerRef = useRef(null); + + useEffect(() => { + const animateGlass = () => { + if (!glassRef.current || !containerRef.current) return; + + const rect = glassRef.current.getBoundingClientRect(); + const containerRect = containerRef.current.getBoundingClientRect(); + + let newX = position.x + velocity.x; + let newY = position.y + velocity.y; + let newVelocityX = velocity.x; + let newVelocityY = velocity.y; + + if (newX <= 0 || newX + rect.width >= containerRect.width) { + newVelocityX = -velocity.x; + newX = Math.max(0, Math.min(newX, containerRect.width - rect.width)); + } + + if (newY <= 0 || newY + rect.height >= containerRect.height) { + newVelocityY = -velocity.y; + newY = Math.max(0, Math.min(newY, containerRect.height - rect.height)); + } + + setPosition({ x: newX, y: newY }); + setVelocity({ x: newVelocityX, y: newVelocityY }); + }; + + const intervalId = setInterval(animateGlass, 16); + + return () => clearInterval(intervalId); + }, [position, velocity]); + + return ( +
+
+
+ ); +} diff --git a/components/home/hero.tsx b/components/home/hero.tsx new file mode 100644 index 0000000..50fbac4 --- /dev/null +++ b/components/home/hero.tsx @@ -0,0 +1,25 @@ +import { WordMark } from "../word-mark"; + +export function HeroSection() { + return ( +
+
+
+ +
+
+
+

My name is...

+

+ Jideofor Owen Enukoha-Efobi +

+

+ Full Stack Web Developer that builds beautiful and functional + websites +

+
+
+
+
+ ); +} diff --git a/components/home/mobile.tsx b/components/home/mobile.tsx new file mode 100644 index 0000000..681830c --- /dev/null +++ b/components/home/mobile.tsx @@ -0,0 +1,42 @@ +import SocialList from "../social-list"; +import { LinkPreview } from "../ui/link-preview"; + +export default function MobilePage() { + return ( +
+

About Me

+
+

+ I love the concept of{" "} + minimalism, + as demonstrated by this project. +

+

A multilinguist, I speak English, French, Yoruba, and Igbo.

+

+ When I'm not designing, prototyping, or building something new, + I'm playing{" "} + + chess + {" "} + or indulging in{" "} + + nature + {" "} + and{" "} + + monochrome + {" "} + photography. +

+

+ I also occasionally attempt to take my{" "} + + blog + {" "} + seriously. +

+
+ +
+ ); +} diff --git a/components/mdx-components.tsx b/components/mdx-components.tsx new file mode 100644 index 0000000..b965196 --- /dev/null +++ b/components/mdx-components.tsx @@ -0,0 +1,18 @@ +"use client"; + +import Image from "next/image"; +import { useMDXComponent } from "next-contentlayer/hooks"; + +const components = { + Image, +}; + +interface MdxProps { + code: string; +} + +export function Mdx({ code }: MdxProps) { + const Component = useMDXComponent(code); + + return ; +} diff --git a/components/mode-toggle.tsx b/components/mode-toggle.tsx new file mode 100644 index 0000000..13ec1f9 --- /dev/null +++ b/components/mode-toggle.tsx @@ -0,0 +1,44 @@ +"use client" + +import { useTheme } from "next-themes" + +export function ModeToggle() { + const { setTheme, theme } = useTheme() + + return ( + + ) +} diff --git a/components/navigation.tsx b/components/navigation.tsx new file mode 100644 index 0000000..f8f2141 --- /dev/null +++ b/components/navigation.tsx @@ -0,0 +1,64 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { useState } from "react"; +import Burger from "./burger"; + +export default function Navigation() { + const pathname = usePathname(); + const [active, setActive] = useState(false); + return ( + <> + setActive(!active)} /> +
+
    +
  • + + Home + +
  • +
  • + + Blog + +
  • +
+
+ + ); +} diff --git a/components/social-list.tsx b/components/social-list.tsx new file mode 100644 index 0000000..f36ff1b --- /dev/null +++ b/components/social-list.tsx @@ -0,0 +1,48 @@ +import Twitter from "@/assets/icons/twitter"; +import GitHub from "@/assets/icons/github"; +import config from "@/lib/config"; +import Link from "next/link"; +import Telegram from "@/assets/icons/telegram"; +import Gmail from "@/assets/icons/mail"; + +export default function SocialList({ className }: { className?: string }) { + return ( +
+ + + + + + + + + + + + +
+ ); +} diff --git a/components/theme-provider.tsx b/components/theme-provider.tsx new file mode 100644 index 0000000..ffac782 --- /dev/null +++ b/components/theme-provider.tsx @@ -0,0 +1,9 @@ +"use client" + +import * as React from "react" +import { ThemeProvider as NextThemesProvider } from "next-themes" +import type { ThemeProviderProps } from "next-themes/dist/types" + +export function ThemeProvider({ children, ...props }: ThemeProviderProps) { + return {children} +} diff --git a/components/ui/link-preview.tsx b/components/ui/link-preview.tsx new file mode 100644 index 0000000..115f939 --- /dev/null +++ b/components/ui/link-preview.tsx @@ -0,0 +1,158 @@ +"use client"; + +import * as HoverCardPrimitive from "@radix-ui/react-hover-card"; +import Image from "next/image"; +import { encode } from "qss"; +import React from "react"; +import { + AnimatePresence, + motion, + useMotionValue, + useSpring, +} from "framer-motion"; +import Link from "next/link"; +import { cn } from "@/lib/utils"; + +type LinkPreviewProps = { + children: React.ReactNode; + url: string; + className?: string; + width?: number; + height?: number; + quality?: number; + layout?: string; +} & ( + | { isStatic: true; imageSrc: string } + | { isStatic?: false; imageSrc?: never } +); + +export const LinkPreview = ({ + children, + url, + className, + width = 200, + height = 125, + quality = 50, + layout = "fixed", + isStatic = false, + imageSrc = "", +}: LinkPreviewProps) => { + let src; + if (!isStatic) { + const params = encode({ + url, + screenshot: true, + meta: false, + embed: "screenshot.url", + colorScheme: "dark", + "viewport.isMobile": true, + "viewport.deviceScaleFactor": 1, + "viewport.width": width * 3, + "viewport.height": height * 3, + }); + src = `https://api.microlink.io/?${params}`; + } else { + src = imageSrc; + } + + const [isOpen, setOpen] = React.useState(false); + + const [isMounted, setIsMounted] = React.useState(false); + + React.useEffect(() => { + setIsMounted(true); + }, []); + + const springConfig = { stiffness: 100, damping: 15 }; + const x = useMotionValue(0); + + const translateX = useSpring(x, springConfig); + + const handleMouseMove = (event: any) => { + const targetRect = event.target.getBoundingClientRect(); + const eventOffsetX = event.clientX - targetRect.left; + const offsetFromCenter = (eventOffsetX - targetRect.width / 2) / 2; // Reduce the effect to make it subtle + x.set(offsetFromCenter); + }; + + return ( + <> + {isMounted ? ( +
+ hidden image +
+ ) : null} + + { + setOpen(open); + }} + > + + {children} + + + + + {isOpen && ( + + + preview image + + + )} + + + + + ); +}; diff --git a/components/ui/separator.tsx b/components/ui/separator.tsx new file mode 100644 index 0000000..12d81c4 --- /dev/null +++ b/components/ui/separator.tsx @@ -0,0 +1,31 @@ +"use client" + +import * as React from "react" +import * as SeparatorPrimitive from "@radix-ui/react-separator" + +import { cn } from "@/lib/utils" + +const Separator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>( + ( + { className, orientation = "horizontal", decorative = true, ...props }, + ref + ) => ( + + ) +) +Separator.displayName = SeparatorPrimitive.Root.displayName + +export { Separator } diff --git a/components/word-mark.tsx b/components/word-mark.tsx new file mode 100644 index 0000000..e9faea5 --- /dev/null +++ b/components/word-mark.tsx @@ -0,0 +1,14 @@ +import localFont from "next/font/local"; +const wordmark = localFont({ + src: "../assets/fonts/wordmark.ttf", +}); + +export function WordMark({ className }: { className?: string }) { + return ( +
+

+ Owenstack +

+
+ ); +} diff --git a/content/pages/about.mdx b/content/pages/about.mdx new file mode 100644 index 0000000..afbb4f0 --- /dev/null +++ b/content/pages/about.mdx @@ -0,0 +1,22 @@ +--- +title: About +description: About the site +--- + +Blandit libero volutpat sed cras ornare arcu. Cursus sit amet dictum sit amet. Nunc vel risus commodo viverra maecenas accumsan. Libero id faucibus nisl tincidunt eget nullam non nisi est. Varius quam quisque id diam vel quam. Id donec ultrices tincidunt arcu non. + +## Consent + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Volutpat sed cras ornare arcu. Nibh ipsum consequat nisl vel pretium lectus quam id leo. A arcu cursus vitae congue. Amet justo donec enim diam. Vel pharetra vel turpis nunc eget lorem. Gravida quis blandit turpis cursus in. Semper auctor neque vitae tempus. Elementum facilisis leo vel fringilla est ullamcorper eget nulla. Imperdiet nulla malesuada pellentesque elit eget. + +Felis donec et odio pellentesque diam volutpat commodo sed. + +Tortor consequat id porta nibh. Fames ac turpis egestas maecenas pharetra convallis posuere morbi leo. Scelerisque fermentum dui faucibus in. Tortor posuere ac ut consequat semper viverra. + +## Information we collect + +Amet justo donec enim diam. In hendrerit gravida rutrum quisque non. Hac habitasse platea dictumst quisque sagittis purus sit. + +## How we use your Information + +Ut sem nulla pharetra diam sit amet nisl suscipit adipiscing. Consectetur adipiscing elit pellentesque habitant. Ut tristique et egestas quis ipsum suspendisse ultrices gravida. diff --git a/content/posts/building-a-restful-api-with-nodejs.mdx b/content/posts/building-a-restful-api-with-nodejs.mdx new file mode 100644 index 0000000..75581cb --- /dev/null +++ b/content/posts/building-a-restful-api-with-nodejs.mdx @@ -0,0 +1,754 @@ +--- +title: 'Building a RESTful API with Node.js' +description: 'Simplified explanation on building a RESTful API with NodeJS' +date: '2024-08-30' +--- + +## Introduction + +Hello, developers! In today's digital world, creating robust and efficient RESTful APIs is a fundamental skill for any backend developer. If you're ready to dive into the world of Node.js and learn how to build RESTful APIs, you're in the right place. In this comprehensive guide, we'll take you through every step of the process, from understanding REST principles to deploying your API in a production environment. + +## **Chapter 1: What is a RESTful API?** + +Before we start coding, let's get a solid understanding of REST (Representational State Transfer) principles. + +### **Understanding REST** + +**REST** is an architectural style for designing networked applications. At its core, REST defines a set of constraints that promote simplicity, scalability, and statelessness in the communication between clients (such as web browsers or mobile apps) and servers. + +Key REST Principles: + +1. **Resources:** In REST, everything is considered a resource, which can be an object, data, or service. These resources are uniquely identified by URLs, such as `https://api.example.com/users`. + +2. **HTTP Methods:** REST uses standard HTTP methods to perform operations on resources: + + - `GET`: Retrieve data (e.g., reading a user's profile). + + - `POST`: Create new resources (e.g., adding a new user). + + - `PUT`: Update existing resources (e.g., modifying user information). + + - `DELETE`: Remove resources (e.g., deleting a user's account). + +3. **Stateless:** RESTful interactions are stateless, meaning each request from a client to a server must contain all the information needed to understand and process the request. The server doesn't store any client state between requests. + +4. **Uniform Interface:** REST encourages a uniform and consistent interface for interacting with resources. This simplifies both client and server implementations. + +### **Example:** + +Let's say you're building a RESTful API for a blog platform. In this context: + +- **Resources:** Resources could include blog posts, comments, and user profiles. Each of these is represented by a unique URL, such as `/posts/123` for a specific blog post. + +- **HTTP Methods:** You'd use HTTP methods to interact with these resources. For instance: + + - `GET /posts/123`: Retrieve the content of a specific blog post. + + - `POST /posts`: Create a new blog post. + + - `PUT /posts/123`: Update the content of a specific blog post. + + - `DELETE /posts/123`: Delete a specific blog post. + +- **Stateless:** Each request to your API should contain all the necessary information. For instance, if a client wants to update a blog post, they should send the entire updated content within the request; the server won't remember previous interactions. + +- **Uniform Interface:** You'd consistently design your API endpoints. For example, using clear and predictable URL structures for all resources, such as `/posts`, `/comments`, and `/users`. This consistency makes it easier for clients to understand and interact with your API. + +Understanding these fundamental principles of REST is crucial as we proceed with building a RESTful API using Node.js. It will guide our design and development decisions to create an efficient and maintainable API. + +## **Chapter 2: Setting Up Your Node.js Environment** + +To build a RESTful API with Node.js, you need to set up your development environment. This foundational step is crucial for a smooth development journey. + +**Installation of Node.js and npm** + +1. **Node.js Installation:** Begin by downloading and installing Node.js from the official website ([**https://nodejs.org/**](https://nodejs.org/)). Node.js includes both the Node runtime and npm, the Node Package Manager. + +2. **Verify Installation:** To ensure a successful installation, open your command line (or terminal) and run the following commands: + + `node -v npm -v` + + *You should see the versions of Node.js and npm displayed, confirming the installation.* + + ### **Project Structure** + + Organizing your project structure is crucial for maintainability. Here's an example of a basic project structure: + + ```javascript + my-api/ + │ + ├── node_modules/ + │ + ├── src/ + │ ├── controllers/ + │ ├── models/ + │ ├── routes/ + │ ├── app.js + │ + ├── package.json + ├── package-lock.json + ├── .gitignore + ├── README.md + ``` + + In this structure: + + - `node_modules/` stores your project's dependencies. + + - `src/` is where you'll place your API code, including controllers, models, and routes. + + - `app.js` is the entry point of your application. + + - `package.json` and `package-lock.json` list project dependencies and metadata. + + - `.gitignore` excludes certain files and directories from version control. + + - `README.md` contains documentation for your project. + + Let's say you want to create a basic Node.js project and set up Visual Studio Code. You can follow these steps: + + 1. Install Node.js by downloading the installer from the official website and following the installation instructions. + + 2. After installing Node.js, open your command line (or terminal) and run the following commands to verify the installation: + + ```javascript + node -v + npm -v + ``` + + *You should see the versions of Node.js and npm displayed.* + + 3. Download and install Visual Studio Code from the VSCode website. + + 4. Open VSCode and create a new workspace or open an existing project folder. + + 5. Install essential extensions by searching for "Node.js", "npm", and "ESLint" in the Extensions sidebar. + + 6. Customize your workspace settings, including code formatting and linting, based on your preferences. + + By completing these steps, you'll have a well-configured development environment ready for building your RESTful API with Node.js. You'll be set to move on to the next chapters, where you'll dive into the practical aspects of API development. + +## **Chapter 3: Creating Your First API Endpoint** + +Now that we've laid the groundwork for building a RESTful API, it's time to roll up our sleeves and start coding. We'll create a basic API endpoint using Node.js and the Express.js framework. This will be your first step towards building a fully functional RESTful API. + +### **Setting Up Express.js** + +First, make sure you have Node.js and npm installed on your system. Then, create a new directory for your project and initialize it with npm: + +```bash +mkdir my-api +cd my-api +npm init -y +``` + +Next, install Express.js as a dependency: + +`npm install express` + +### **Creating Your First API Endpoint** + +Now, let's create a simple Express.js server and define our first API endpoint. Create a file called `app.js` (or any name you prefer) and add the following code: + +```javascript +const express = require('express'); +const app = express(); +const port = 3000; // You can choose any port you like + +// Define a basic route +app.get('/', (req, res) => { + res.send('Welcome to my API!'); +}); + +// Start the server +app.listen(port, () => { + console.log(`Server is running on port ${port}`); +}); +``` + +*In this example, we've:* + +1. *Imported the Express.js library and created an Express application.* + +2. *Defined a route using* `app.get('/')`*. This route responds to HTTP GET requests at the root URL ('/'). When someone accesses your API's root, they'll receive the "Welcome to my API!" message.* + +3. *Started the server on the specified port (in this case, 3000) and logged a message to the console.* + + ### **Testing Your First Endpoint** + + To test your API, open your terminal, navigate to your project directory, and run: + + `node app.js` + + Your server should start, and you'll see the "Server is running on port 3000" message in your console. + + Now, open a web browser or use a tool like Postman to send a GET request to `http://localhost:3000`. You should receive the "Welcome to my API!" response. + + Congratulations! You've created your first API endpoint using Node.js and Express.js. + +## **Chapter 4: Handling Data with CRUD Operations** + +A RESTful API isn't complete without data storage. We'll delve into the essential aspects of data handling in your RESTful API built with Node.js. To illustrate these concepts, we'll use a simple example of a "To-Do List" API. We'll cover how to perform CRUD operations (Create, Read, Update, Delete) on this list. + +### **Choosing a Database** + +Before diving into CRUD operations, you need a database to store your data. In our example, we'll use MongoDB, a popular NoSQL database, and Mongoose, an ODM (Object Data Modeling) library for MongoDB in Node.js. + +```javascript +// Example: Setting up MongoDB with Mongoose +const mongoose = require('mongoose'); + +mongoose.connect('mongodb://localhost/todo-app', { + useNewUrlParser: true, + useUnifiedTopology: true, +}); + +const todoSchema = new mongoose.Schema({ + task: String, + completed: Boolean, +}); + +const Todo = mongoose.model('Todo', todoSchema); +``` + +### **Create (POST) Operation** + +To add new tasks to our To-Do List, we'll implement the Create operation. Clients can send a POST request with a JSON payload containing the task details. + +```javascript +// Example: Creating a new To-Do task +app.post('/todos', async (req, res) => { + const { task, completed } = req.body; + const newTodo = new Todo({ task, completed }); + + try { + const savedTodo = await newTodo.save(); + res.status(201).json(savedTodo); + } catch (err) { + res.status(400).json({ error: err.message }); + } +}); +``` + +### **Read (GET) Operation** + +To retrieve tasks from the To-Do List, we'll implement the Read operation. Clients can send GET requests to fetch all tasks or a specific task by its ID. + +```javascript +// Example: Retrieving all To-Do tasks +app.get('/todos', async (req, res) => { + try { + const todos = await Todo.find(); + res.status(200).json(todos); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); + +// Example: Retrieving a specific To-Do task by ID +app.get('/todos/:id', async (req, res) => { + const { id } = req.params; + + try { + const todo = await Todo.findById(id); + if (!todo) { + return res.status(404).json({ error: 'Task not found' }); + } + res.status(200).json(todo); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); +``` + +### **Update (PUT) Operation** + +To modify existing tasks in our To-Do List, we'll implement the Update operation. Clients can send a PUT request with updated task details. + +```javascript +// Example: Updating a To-Do task by ID +app.put('/todos/:id', async (req, res) => { + const { id } = req.params; + const { task, completed } = req.body; + + try { + const updatedTodo = await Todo.findByIdAndUpdate(id, { task, completed }, { new: true }); + if (!updatedTodo) { + return res.status(404).json({ error: 'Task not found' }); + } + res.status(200).json(updatedTodo); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); +``` + +### **Delete (DELETE) Operation** + +To remove tasks from our To-Do List, we'll implement the Delete operation. Clients can send a DELETE request with the task's ID to delete it. + +```javascript +// Example: Deleting a To-Do task by ID +app.delete('/todos/:id', async (req, res) => { + const { id } = req.params; + + try { + const deletedTodo = await Todo.findByIdAndDelete(id); + if (!deletedTodo) { + return res.status(404).json({ error: 'Task not found' }); + } + res.status(204).send(); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); +``` + +We've seen how to create, read, update, and delete data in your RESTful API using Node.js and MongoDB. These operations form the backbone of many APIs, and mastering them is essential for building robust and functional applications. + +## **Chapter 5: Structuring Your API** + +As your API grows, maintaining code structure becomes crucial. We'll introduce you to the concept of routing in Express.js, showing you how to organize your API endpoints efficiently. Proper structuring ensures that your API remains scalable and easy to manage as it grows. + +**Route Organization** + +To begin, let's consider a simple example. Imagine you're building a blog API that handles posts and comments. Instead of having all your routes and logic in a single file, you'll create a directory structure like this: + +```markdown +- src + - routes + - posts.js + - comments.js + - controllers + - postsController.js + - commentsController.js +``` + +Here's how this structure works: + +1. **Routes**: Each resource (posts and comments) has its route file. These route files define the API endpoints related to that resource. + + **Example - posts.js**: + + ```javascript + const express = require('express'); + const router = express.Router(); + const postsController = require('../controllers/postsController'); + + router.get('/', postsController.getAllPosts); + router.get('/:id', postsController.getPostById); + router.post('/', postsController.createPost); + router.put('/:id', postsController.updatePost); + router.delete('/:id', postsController.deletePost); + + module.exports = router; + ``` + +2. **Controllers**: The controllers handle the logic for each endpoint. Separating this logic from the route definitions keeps your code organized and makes it easier to test. + + **Example - postsController.js**: + + ```javascript + const { Post } = require('../models'); // Assuming you have a Post model + + module.exports = { + getAllPosts: async (req, res) => { + try { + const posts = await Post.find(); + res.json(posts); + } catch (error) { + res.status(500).json({ error: 'Internal Server Error' }); + } + }, + // Other controller methods for handling specific endpoints + }; + ``` + +3. **Middleware**: You can also use middleware functions to handle tasks like authentication or validation before reaching the controller. Middleware can be applied globally or to specific routes as needed. + + ```javascript + // Middleware function for authentication + function authenticate(req, res, next) { + // Check authentication logic here + if (authenticated) { + next(); // Continue to the next middleware or controller + } else { + res.status(401).json({ error: 'Unauthorized' }); + } + } + + // Applying middleware to a specific route + router.post('/', authenticate, postsController.createPost); + ``` + +### **Benefits** + +This structured approach offers several benefits: + +- **Maintainability**: With separate route and controller files, your codebase becomes easier to navigate and update. + +- **Scalability**: As your API grows, you can continue adding more routes and controllers without cluttering a single file. + +- **Testing**: Isolating controller logic allows for efficient unit testing, ensuring each function works as expected. + +- **Collaboration**: When working on a team, this structure simplifies collaboration as team members can focus on specific routes or controllers. + +By structuring your API in this way, you'll have a solid foundation to build upon as you continue to develop and expand your Node.js RESTful API. + +## **Chapter 6: Middleware and Authentication** + +Security is paramount in API development. Middleware and authentication are essential for ensuring your API is secure and operates smoothly. We'll explore techniques like JWT (JSON Web Tokens) for secure user authentication. + +### **Middleware in Express.js** + +Middleware functions in Express.js are intermediary functions that process requests before they reach the final route handler. They provide a way to perform tasks such as logging, authentication, and data validation. Here's an example of middleware in action: + +```javascript +const express = require('express'); +const app = express(); + +// Custom middleware function +const logger = (req, res, next) => { + console.log(`Request received at ${new Date()}`); + next(); // Move to the next middleware or route handler +}; + +app.use(logger); // Register the middleware globally + +app.get('/', (req, res) => { + res.send('Hello, World!'); +}); + +app.listen(3000, () => { + console.log('Server is running on port 3000'); +}); +``` + +In this example, the `logger` middleware logs the timestamp of each incoming request before passing it to the route handler. Middleware like this can be used to authenticate users, handle CORS (Cross-Origin Resource Sharing), and perform various other tasks. + +### **User Authentication with JWT** + +User authentication is crucial for securing your API. One common method is using JSON Web Tokens (JWT). Here's a simplified example of JWT authentication: + +```javascript +const express = require('express'); +const app = express(); +const jwt = require('jsonwebtoken'); + +// Secret key for JWT +const secretKey = 'your-secret-key'; + +// Middleware for verifying JWT +const verifyToken = (req, res, next) => { + const token = req.headers.authorization; + + if (!token) { + return res.status(401).json({ message: 'Unauthorized' }); + } + + jwt.verify(token, secretKey, (err, decoded) => { + if (err) { + return res.status(401).json({ message: 'Invalid token' }); + } + req.user = decoded; // Store user information in the request object + next(); + }); +}; + +app.use(express.json()); + +app.post('/login', (req, res) => { + // Authenticate the user (e.g., check username and password) + const user = { id: 1, username: 'exampleuser' }; + + // Create and sign a JWT + jwt.sign(user, secretKey, (err, token) => { + if (err) { + return res.status(500).json({ message: 'Error generating token' }); + } + res.json({ token }); + }); +}); + +app.get('/protected', verifyToken, (req, res) => { + res.json({ message: 'This is a protected route', user: req.user }); +}); + +app.listen(3000, () => { + console.log('Server is running on port 3000'); +}); +``` + +In this example, when a user logs in, they receive a JWT token. To access protected routes like `/protected`, the client must include the token in the request's `Authorization` header. The `verifyToken` middleware checks the token's validity, allowing access only to authenticated users. + +By mastering middleware and authentication, you can enhance the security and functionality of your Node.js RESTful API, ensuring that it's well-prepared to handle user interactions and protect sensitive data. + +## **Chapter 7: Error Handling and Validation** + +No API is error-free, but you can handle errors gracefully. You'll ensure your API responds with meaningful error messages and maintains data integrity. Let's explore this topic with an example: + +### **Error Handling:** + +#### 1. Centralized Error Handling Middleware + +To handle errors consistently across your API, you can create centralized error-handling middleware. This middleware catches errors thrown by other parts of your application and sends appropriate responses to clients. Here's a simplified example using Express.js: + +```javascript +// Error handling middleware +app.use((err, req, res, next) => { + // Handle different types of errors + if (err instanceof CustomError) { + // Handle specific custom error + res.status(err.statusCode).json({ error: err.message }); + } else { + // Handle generic server errors + res.status(500).json({ error: "Internal Server Error" }); + } +}); +``` + +#### 2. Custom Error Classes + +Creating custom error classes can make error handling more structured. For instance, you can define a custom `BadRequestError` class for client input validation errors: + +```javascript +class BadRequestError extends Error { + constructor(message) { + super(message); + this.name = "BadRequestError"; + this.statusCode = 400; + } +} + +// Usage +if (!isValidInput) { + throw new BadRequestError("Invalid input data"); +} +``` + +### **Data Validation:** + +#### 1. Input Validation with Express Validator + +Express Validator is a popular middleware for validating input data. You can use it to check request parameters, body, and query strings. Here's a basic example: + +```javascript +const { body, validationResult } = require("express-validator"); + +// Validation middleware +app.post( + "/api/users", + [ + body("email").isEmail().normalizeEmail(), + body("password").isLength({ min: 6 }), + ], + (req, res) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ errors: errors.array() }); + } + + // Proceed with data processing + } +); +``` + +#### 2. Database Integrity Checks + +When working with databases, ensure data integrity through database constraints and transactions. For example, you can use a unique constraint to prevent duplicate entries in a users table: + +```sql +CREATE TABLE users ( + id serial PRIMARY KEY, + username VARCHAR (255) UNIQUE NOT NULL, + -- other columns +); +``` + +With these techniques, you can handle errors gracefully and validate data effectively in your Node.js API, ensuring a smooth and secure user experience. + +## **Chapter 8: Testing Your API** + +Quality assurance is crucial before deploying your API. We'll introduce you to testing frameworks like Mocha and Chai, teaching you how to write unit and integration tests. You'll gain confidence in your API's reliability. + +**The Importance of Testing** + +Testing serves several essential purposes in API development: + +1. **Bug Detection:** Testing helps identify and fix bugs and issues in your API before they reach production, reducing the risk of unexpected problems. + +2. **Ensuring Functionality:** It verifies that your API endpoints work as expected, handling different HTTP methods and scenarios correctly. + +3. **Improving Code Quality:** Testing encourages good coding practices by promoting modularity and testable code. + +### **Types of API Testing** + +There are various types of testing you can perform on your API: + +1. **Unit Testing:** Focuses on testing individual functions or modules in isolation. For example, you might write unit tests for specific API routes or middleware functions. + +2. **Integration Testing:** Tests how different parts of your API work together. For instance, you can test how your API interacts with a database or external services. + +3. **End-to-End (E2E) Testing:** This involves testing the entire flow of a request from the client to the server and back. It ensures that all components work together harmoniously. + +### **Testing Tools and Libraries** + +Here are some popular testing tools and libraries for Node.js API testing: + +1. **Mocha:** A flexible and widely used testing framework for Node.js. It provides a clean structure for writing tests and supports various assertion libraries. + +2. **Chai:** A popular assertion library that works well with Mocha. It provides expressive and readable assertions for your test cases. + +3. **Supertest:** A library for making HTTP requests to your API during testing. It simplifies the process of sending requests and validating responses. + +### **Example: Writing a Unit Test** + +Let's consider an example of writing a unit test for a simple API endpoint using Mocha and Chai. Assume you have an API endpoint that retrieves a list of users from a database. + +```javascript +// Import necessary modules and dependencies +const chai = require('chai'); +const chaiHttp = require('chai-http'); +const app = require('../app'); // Replace with your app file + +chai.use(chaiHttp); +const expect = chai.expect; + +describe('GET /users', () => { + it('should return a list of users', (done) => { + chai + .request(app) + .get('/users') + .end((err, res) => { + expect(res).to.have.status(200); + expect(res.body).to.be.an('array'); + expect(res.body).to.have.length.above(0); + done(); + }); + }); +}); +``` + +In this example, we're testing the `/users` endpoint by making a GET request to it using `chai-http`. We then use Chai's assertions to check if the response has a 200 status code, contains an array, and has at least one user in the response body. + +By writing and running tests like this, you can ensure that your API endpoints behave as expected and catch any regressions as you make changes to your codebase. + +## **Chapter 9: Documentation** + +In the world of API development, documentation is like the user manual for your API. It provides essential information to other developers on how to use your API effectively. We'll explore the importance of documentation and how to create clear, comprehensive documentation for your RESTful API. + +### **Why Documentation Matters** + +Documentation serves several critical purposes: + +1. **Accessibility:** It makes your API accessible to other developers by explaining its endpoints, request parameters, response formats, and authentication methods. + +2. **Reduced Support Burden:** Good documentation reduces the need for support inquiries, as developers can find answers to their questions in the documentation. + +3. **Onboarding:** It helps new developers understand your API quickly, reducing the learning curve. + +4. **API Consumption:** Documentation aids developers in integrating your API into their applications, leading to increased adoption. + +### **Example: Swagger for API Documentation** + +One popular tool for creating API documentation is Swagger. Here's a brief example of how to use Swagger to document your API in a Node.js application: + +1. **Install Swagger:** Start by installing the `swagger-jsdoc` and `swagger-ui-express` packages via npm. + + ```bash + npm install swagger-jsdoc swagger-ui-express + ``` + +2. **Create Swagger Definition:** Define your API's documentation in a separate JavaScript file, often named `swagger.js`. Here's a minimal example: + + ```javascript + const swaggerJsdoc = require('swagger-jsdoc'); + + const options = { + swaggerDefinition: { + openapi: '3.0.0', + info: { + title: 'Sample API Documentation', + version: '1.0.0', + description: 'Documentation for a sample RESTful API', + }, + }, + apis: ['routes/*.js'], // Path to your route files + }; + + const specs = swaggerJsdoc(options); + + module.exports = specs; + ``` + +3. **Integrate Swagger UI:** In your Express.js application, use `swagger-ui-express` to set up a route for accessing the Swagger documentation. + + ```javascript + const express = require('express'); + const swaggerUi = require('swagger-ui-express'); + const swaggerSpec = require('./swagger'); + + const app = express(); + + app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec)); + + // ...your other routes and middleware + + app.listen(3000, () => { + console.log('Server is running on port 3000'); + }); + ``` + +4. **View Documentation:** Start your Node.js server and access the API documentation at `http://localhost:3000/api-docs`. You'll find interactive documentation where users can explore your API's endpoints, make test requests, and see example responses. + +By following this example, you can quickly create documentation for your RESTful API using Swagger. Remember to document each endpoint, including descriptions, request parameters, response formats, and any authentication requirements. Well-documented APIs are more likely to be adopted and appreciated by other developers. + +## **Chapter 10: Deployment and Scaling** + +In this final chapter, we'll explore the crucial steps of deploying your Node.js RESTful API and scaling it to meet the demands of real-world applications. Let's delve into these topics with an example: + +### **Deployment** + +#### Example: Deploying to Heroku + +Heroku is a popular platform-as-a-service (PaaS) that simplifies the deployment process. Here's a simplified guide to deploying your Node.js API to Heroku: + +1. **Create a Heroku Account:** If you don't have one, sign up for a Heroku account. + +2. **Install Heroku CLI:** Download and install the Heroku Command Line Interface (CLI) to interact with Heroku from your terminal. + +3. **Prepare Your API:** Ensure your API code is well-organized and includes a `package.json` file with dependencies listed. + +4. **Initialize a Git Repository:** If your project isn't already in a Git repository, run `git init` in your project directory. + +5. **Login to Heroku:** Use the `heroku login` command to log in to your Heroku account. + +6. **Create a Heroku App:** Run `heroku create` to create a new Heroku app. Heroku will provide you with a unique app URL. + +7. **Configure Environment Variables:** If your API relies on environment variables (e.g., database connection strings or API keys), set them in your Heroku app using `heroku config:set`. + +8. **Deploy Your App:** Push your code to Heroku's remote repository with `git push heroku master`. Heroku will automatically build and deploy your app. + +9. **Open Your App:** After a successful deployment, open your app in your web browser using `heroku open`. + +### **Scaling** + +#### Example: Scaling with Load Balancers + +As your API gains popularity, you may need to scale it to handle increased traffic. One way to achieve this is by using load balancers to distribute incoming requests across multiple server instances. Let's consider an example with NGINX: + +1. **Install and Configure NGINX:** Set up NGINX on one or more server instances. Configure NGINX to act as a reverse proxy, forwarding incoming requests to your Node.js API servers. + +2. **Launch Multiple API Server Instances:** Run multiple instances of your Node.js API on different ports or servers. Ensure they can handle requests independently. + +3. **Configure NGINX Load Balancer:** Update your NGINX configuration to include load balancing directives, specifying the backend servers and load balancing algorithm (e.g., round-robin). + +4. **Traffic Distribution:** NGINX will now distribute incoming requests evenly across your API server instances, ensuring high availability and improved performance. + +By deploying to Heroku and scaling with a load balancer like NGINX, you can handle increasing traffic while maintaining the reliability of your Node.js RESTful API. Remember that scaling strategies may vary depending on your specific application requirements and infrastructure. + +In conclusion, mastering the deployment and scaling of your Node.js RESTful API is essential for making it accessible to users and ensuring its performance under heavy loads. These skills will help you take your API from development to production, serving users reliably and efficiently. + +### **Conclusion** + +Congratulations! You've completed this comprehensive guide on building a RESTful API with Node.js. You've gained a deep understanding of REST principles, created API endpoints, handled data, implemented security, and learned how to deploy and scale your API. Now, armed with this knowledge, you're ready to build powerful and scalable web services that can serve a multitude of applications. Keep coding, keep learning, and continue to explore the endless possibilities of Node.js API development. Happy coding! diff --git a/content/posts/mastering-nodejs-a-beginner-guide.mdx b/content/posts/mastering-nodejs-a-beginner-guide.mdx new file mode 100644 index 0000000..a493809 --- /dev/null +++ b/content/posts/mastering-nodejs-a-beginner-guide.mdx @@ -0,0 +1,469 @@ +--- +title: 'Mastering Node.js: A Beginner Guide' +description: 'The ins and outs of understanding NodeJS' +date: '2024-08-30' +--- + +## Introduction + +Hello, fellow developers! Today, we embark on an exciting journey into the world of Node.js. Whether you're just starting your coding adventure or you're a seasoned developer looking to expand your skillset, Node.js has a lot to offer. In this beginner's guide, we'll dive deep into Node.js, exploring its core concepts and practical applications, all while keeping it engaging and informative. + +## Chapter 1: What is Node.js? + +To kick things off, let's get a solid understanding of Node.js. Node.js is an open-source, server-side runtime environment that allows you to run JavaScript code outside of a web browser. It's built on Chrome's V8 JavaScript engine, making it incredibly fast and efficient. Node.js empowers developers to build scalable, high-performance applications, making it a popular choice in the world of back-end development. + +## Chapter 2: Setting Up Your Environment + +Before we can master Node.js, we need to set up our development environment. Whether you're using Windows, macOS, or Linux, we'll walk you through the installation process step by step. Windows is a popular operating system among developers, and with the right tools and configurations, you can enjoy a seamless Node.js development experience. + +### **Installation** + +1. **Node.js**: The first step is to install Node.js itself. Visit the official Node.js website ([**https://nodejs.org/**](https://nodejs.org/)) and download the Windows Installer (.msi) for the LTS (Long-Term Support) version. Run the installer and follow the on-screen instructions. Node.js comes bundled with npm, the Node Package Manager. + + [Node.js](https://drive.google.com/file/d/1ZJSLafyTwigES-4jplcQ9_jbxUhp-5zW/view?usp=drive_link) + +2. **Visual Studio Code**: For a fantastic code editing experience, we recommend installing Visual Studio Code (VS Code), a lightweight yet powerful code editor developed by Microsoft. Download it from the VS Code website ([**https://code.visualstudio.com/**](https://code.visualstudio.com/)) and follow the installation instructions. + +### *Configuring Visual Studio Code* + +1. **Extensions**: VS Code offers a wide range of extensions to enhance your Node.js development. Install extensions like "Node.js" and "npm Intellisense" to make your coding process more efficient. + +2. **Integrated Terminal**: VS Code comes with an integrated terminal, which is incredibly handy for running Node.js commands and scripts. You can open it by pressing `` Ctrl + ` ``(backtick). It will automatically detect your Node.js installation. + +3. **Debugging**: VS Code offers excellent debugging capabilities for Node.js. You can set breakpoints, inspect variables, and step through your code with ease. + +### *Working with the Windows Command Line* + +When working with Node.js on Windows, you'll often find yourself using the command line. Here are some essential Windows-specific tips: + +1. **File Paths**: In Windows, file paths use backslashes (e.g., `C:\Users\Jack\Documents`) instead of forward slashes. Be mindful of this when specifying file paths in your Node.js code. + +2. **Environment Variables**: To set environment variables for your Node.js projects, you can use the Windows Environment Variables dialog. This is useful for storing sensitive information like API keys or database connection strings securely. + +### *WSL (Windows Subsystem for Linux)* + +For some advanced scenarios or when working on projects that require Linux-specific tools, you might consider using the Windows Subsystem for Linux (WSL). WSL allows you to run a Linux distribution alongside your Windows installation, providing a more Linux-like development environment. + +## Chapter 3: Understanding Asynchronous JavaScript + +Imagine you're in a bustling coffee shop, waiting for your favorite brew. Instead of standing in line, staring at the barista, you're free to chat with friends, read a book, or even work on your laptop. That's the essence of asynchronous programming – you don't have to wait for one task to complete before moving on to the next. + +In this chapter, we're going to break down the concept of asynchronous JavaScript into bite-sized, action-packed segments: + +**1. Callbacks**\ +Callbacks are like the action heroes of asynchronous programming. They allow you to define what happens once a particular task is completed. Picture yourself sending an army of functions into battle, each waiting for its turn to execute. Callbacks are your trusty commanders, ensuring everything gets done without a hitch. + +**2. Promises**\ +Promises bring a touch of elegance to your code. They're like written contracts – a commitment to do something when a task is complete, regardless of success or failure. It's like having a crystal ball that lets you peer into the future of your code execution. + +**3. Async/Await**\ +Enter the superheroes of asynchronous code. With async/await, your code becomes a gripping narrative where you await the results of your asynchronous tasks with bated breath. It's like writing a thriller novel where you can pause and resume the action whenever you like. + +## Chapter 4: Modules and npm + +Node.js has a powerful module system that encourages code modularity and reusability. Modules are at the core of structuring Node.js applications, promoting code modularity and reusability. npm, on the other hand, is a powerful tool for managing and sharing packages and libraries with the Node.js community. + +### **Modules in Node.js** + +Node.js uses a CommonJS module system, allowing you to break your code into smaller, manageable pieces. These pieces, known as modules, can be imported and reused across your application. Here's a basic example of creating and using modules: + +```typescript +// Create a module named "myModule.js" +// myModule.js +const greeting = "Hello, "; + +function sayHello(name) { + console.log(greeting + name); +} + +module.exports = { sayHello }; + +// In another file, you can import and use the module +const myModule = require("./myModule"); +myModule.sayHello("Owen"); // Outputs: Hello, Owen +``` + +*In this example, we create a module named "myModule.js" that exports a function* `sayHello`*. We then import and use this module in another file.* + +### **Using npm to Manage Packages** + +npm is the default package manager for Node.js, and it simplifies the process of installing and managing third-party packages. Here's how you can use npm: + +1. **Initializing a New Project**: To start a new Node.js project, navigate to your project directory in the terminal and run `npm init`. This command will guide you through setting up your project and creating a `package.json` file to manage dependencies. + +2. **Installing Packages**: You can use `npm install` to install packages. For example, to install the popular `express` web framework, you'd run `npm install express`. + +3. **Managing Dependencies**: Your project's dependencies are listed in the `package.json` file. You can manually edit this file to add, remove, or update packages. When you make changes, run `npm install` again to update your project's dependencies. + +4. **Using Installed Packages**: Once a package is installed, you can require it in your code just like your own modules. For instance, with `express`: + + `const express = require("express"); const app = express();` + +By embracing modules and npm, you can leverage a vast ecosystem of libraries and packages to enhance your Node.js applications. This modular approach simplifies code management, encourages code reuse, and streamlines the development process. Let's explore the key concepts with a simple example. + +## Chapter 5: Building Your First Web Server + +It's time to put theory into practice! In this chapter, we'll guide you through building a basic web server using Node.js. You'll learn how to handle HTTP requests and responses, laying the foundation for more complex applications. + +**Setting Up Your Server** + +First, you'll need to create a JavaScript file for your server. Let's call it `server.js`. In this file, we'll require the `http` module, which is a built-in Node.js module for handling HTTP requests and responses. + +`const http = require('http');` + +Next, we'll define a callback function that will be executed every time a request is made to our server. This function receives two arguments: `req` (the request object) and `res` (the response object). + +```typescript +const server = http.createServer((req, res) => { + // Your code for handling requests goes here +}); +``` + +### **Handling Requests** + +nside the callback function, you can determine how your server responds to different types of requests. Let's create a basic example that responds with "Hello, World!" for all incoming requests. + +```typescript +const server = http.createServer((req, res) => { + // Set the response status and headers + res.writeHead(200, { 'Content-Type': 'text/plain' }); + + // Write the response body + res.end('Hello, World!\n'); +}); +``` + +### **Starting the Server** + +Now that we've defined our server, it's time to start it and make it listen for incoming requests. We'll specify the port on which the server should listen, typically port 3000 for development. + +```typescript +const PORT = 3000; + +server.listen(PORT, () => { + console.log(`Server is running on port ${PORT}`); +}); +``` + +### **Testing Your Server** + +To test your server, open your terminal, and navigate to the directory containing `server.js`, and run the following command: + +`node server.js` + +You should see the "Server is running on port 3000" message in your console, indicating that your server is up and running. Now, open your web browser and visit `http://localhost:3000`. You should see "Hello, World!" displayed in your browser. + +Congratulations! You've successfully built your first web server using Node.js. This is just the beginning of what you can achieve with Node.js in backend development. You can expand on this example by handling different routes, serving HTML pages, or even integrating with databases to create dynamic web applications. The possibilities are endless, and Node.js makes it all possible. + +## Chapter 6: Working with Databases + +No back-end development journey is complete without databases. Databases are where you store, manage, and retrieve structured data. Node.js provides various modules and libraries to work with different types of databases, such as SQL databases like MySQL and PostgreSQL or NoSQL databases like MongoDB. + +### **Introduction to Databases** + +Databases are where you store, manage, and retrieve structured data. Node.js provides various modules and libraries to work with different types of databases, such as SQL databases like MySQL and PostgreSQL or NoSQL databases like MongoDB. + +### **Connecting to a Database** + +Connecting to a database is the first step. Here's an example using the popular MongoDB database with the `mongoose` library: + +```typescript +const mongoose = require('mongoose'); + +mongoose.connect('mongodb://localhost/mydatabase', { + useNewUrlParser: true, + useUnifiedTopology: true +}) + .then(() => console.log('Connected to MongoDB')) + .catch(err => console.error('Error connecting to MongoDB', err)); +``` + +### **Performing CRUD Operations** + +Once connected, you can perform CRUD (Create, Read, Update, Delete) operations. Let's look at an example of creating and retrieving data with MongoDB: + +```typescript +// Define a schema and model +const mongoose = require('mongoose'); + +const userSchema = new mongoose.Schema({ + name: String, + email: String +}); + +const User = mongoose.model('User', userSchema); + +// Create a new user +const newUser = new User({ + name: 'John Doe', + email: 'john@example.com' +}); + +newUser.save() + .then(() => console.log('User saved:', newUser)) + .catch(err => console.error('Error saving user:', err)); + +// Retrieve users +User.find() + .then(users => console.log('Users:', users)) + .catch(err => console.error('Error fetching users:', err)); +``` + +### **Querying and Updating Data** + +You can also query and update data based on specific criteria: + +```typescript +// Find a user by email +User.findOne({ email: 'john@example.com' }) + .then(user => { + if (user) { + console.log('Found user:', user); + + // Update user's name + user.name = 'Jane Doe'; + return user.save(); + } else { + console.log('User not found'); + } + }) + .then(updatedUser => console.log('Updated user:', updatedUser)) + .catch(err => console.error('Error:', err)); +``` + +### **Conclusion** + +Whether you choose SQL or NoSQL databases, understanding how to connect, perform CRUD operations, and manipulate data is fundamental for building robust and data-driven applications. In your journey as a Node.js developer, mastering database interactions is a valuable skill. + +## Chapter 7: Creating APIs + +APIs (Application Programming Interfaces) are the backbone of modern web applications. In the context of Node.js, creating APIs enables you to expose your application's functionalities to other services, applications, or even third-party developers. We'll delve into creating RESTful APIs using Node.js, and I'll provide you with some examples to illustrate the concepts. + +**Getting Started:** + +To create APIs with Node.js, you'll need to use a framework like Express.js. Express is a minimal and flexible Node.js web application framework that simplifies the process of building robust APIs. First, ensure you have Express.js installed in your project by running: + +`npm install express` + +**Example: Creating a Simple API** + +Let's start with a basic example. Suppose you want to create an API that retrieves a list of books. Here's a simple Express.js application to achieve this: + +```typescript +const express = require('express'); +const app = express(); +const port = 3000; + +// Sample data (usually you'd fetch this from a database) +const books = [ + { id: 1, title: 'The Great Gatsby' }, + { id: 2, title: 'To Kill a Mockingbird' }, + { id: 3, title: '1984' }, +]; + +// Define a route to get all books +app.get('/api/books', (req, res) => { + res.json(books); +}); + +app.listen(port, () => { + console.log(`Server is running on port ${port}`); +}); +``` + +*In this example, we've created a simple Express server that listens on port 3000. When you make a GET request to '/api/books', it responds with a JSON array containing a list of books.* + +**Testing Your API:** + +You can test this API using tools like Postman or by making HTTP requests in your browser. Simply navigate to '[**http://localhost:3000/api/books**](http://localhost:3000/api/books)', and you should see the list of books returned as JSON. + +**Creating More Advanced APIs:** + +As you progress in your Node.js journey, you can extend your APIs to handle CRUD operations (Create, Read, Update, Delete), implement authentication and authorization, and connect to databases to store and retrieve data. Express.js provides a robust framework to support these advanced features. + +Remember, APIs are the backbone of modern web applications, enabling them to interact with various clients, including web browsers, mobile apps, and IoT devices. + +## Chapter 8: Authentication and Security + +Security is paramount in any application. We'll cover essential security practices, including user authentication, authorization, and data validation, ensuring your Node.js applications are robust and protected. + +### **User Authentication** + +User authentication is the process of verifying the identity of users before granting them access to certain parts of your application. A common way to implement user authentication in Node.js is by using popular packages like Passport.js. + +**Example:** + +```typescript +const passport = require('passport'); +const LocalStrategy = require('passport-local').Strategy; + +// Define a strategy for authenticating users +passport.use(new LocalStrategy( + function(username, password, done) { + // Check the username and password against your database + if (username === 'user' && password === 'password') { + return done(null, { id: 1, username: 'user' }); + } else { + return done(null, false, { message: 'Incorrect username or password' }); + } + } +)); +``` + +### **Authorization** + +Authorization goes hand in hand with authentication. It determines what actions users are allowed to perform within your application based on their roles and permissions. + +**Example:** + +```typescript +// Middleware to check if a user is authenticated +function isAuthenticated(req, res, next) { + if (req.isAuthenticated()) { + return next(); + } + res.redirect('/login'); +} + +// Protect a route with authentication +app.get('/dashboard', isAuthenticated, (req, res) => { + // Only authenticated users can access this route + res.render('dashboard'); +}); +``` + +### **Data Validation** + +Input validation is crucial to prevent security vulnerabilities like SQL injection and cross-site scripting (XSS) attacks. Use packages like `express-validator` to validate user input. + +**Example:** + +```typescript +const { body, validationResult } = require('express-validator'); + +app.post('/create-post', + body('title').trim().isLength({ min: 1 }).withMessage('Title cannot be empty'), + body('content').trim().isLength({ min: 1 }).withMessage('Content cannot be empty'), + (req, res) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ errors: errors.array() }); + } + // Process the valid input and create a post + } +); +``` + +### **Protecting Against Cross-Site Request Forgery (CSRF)** + +Use middleware like `csurf` to protect your application against CSRF attacks, which can trick authenticated users into performing unwanted actions. + +**Example:** + +```typescript +const csrf = require('csurf'); +const csrfProtection = csrf({ cookie: true }); + +// Apply CSRF protection to specific routes +app.get('/payment', csrfProtection, (req, res) => { + // Render a form with CSRF token + res.render('payment', { csrfToken: req.csrfToken() }); +}); + +app.post('/process-payment', csrfProtection, (req, res) => { + // Verify the CSRF token before processing the payment + // ... +}); +``` + +By implementing these security practices in your Node.js application, you'll enhance its resilience against common security threats, providing a safer and more reliable user experience. Always keep security at the forefront of your development efforts. + +## Chapter 9: Deployment and Scaling + +Once your application is ready, you'll want to deploy it to a production environment. We'll explore deployment options and strategies, as well as techniques for scaling your Node.js application to handle increased traffic and demand. + +### **Deployment** + +Deployment involves making your application accessible to users on the internet. Here are some common deployment options for Node.js applications: + +1. **Traditional Hosting**: You can deploy Node.js applications on traditional web hosting platforms like AWS, DigitalOcean, or Heroku. These platforms provide infrastructure and services to host your application. + +2. **Serverless**: Serverless computing, using platforms like AWS Lambda or Azure Functions, allows you to deploy your Node.js code in a serverless environment. It's a cost-effective way to run small, event-driven functions. + +3. **Containerization**: Docker allows you to containerize your Node.js application, making it portable and easy to deploy across various environments using container orchestration tools like Kubernetes. + +**Example**: Let's say you want to deploy a simple Node.js web application using AWS Elastic Beanstalk. After setting up your AWS environment, you can use the AWS CLI or the Elastic Beanstalk console to deploy your code. Here's a simplified command to deploy a Node.js app: + +```bash +eb init -p node.js my-app +eb create my-app-environment +eb deploy +``` + +*This will package and deploy your Node.js application to AWS.* + +### **Scaling** + +As your application gains popularity, you'll need to ensure it can handle increased traffic. Here are some scaling strategies: + +1. **Vertical Scaling**: This involves increasing the resources (CPU, RAM) of your server to handle more traffic. For example, upgrading your virtual machine on AWS. + +2. **Horizontal Scaling**: Instead of upgrading a single server, you add more servers to distribute the load. Load balancers like AWS Elastic Load Balancing can distribute incoming requests across multiple instances of your Node.js application. + +3. **Microservices**: Break your application into smaller, independently scalable microservices. Each microservice can be a Node.js application that handles a specific part of your system. + +**Example**: Let's say you have an e-commerce website built with Node.js. As the holiday season approaches, you expect a surge in traffic. To scale horizontally, you can use AWS Elastic Load Balancing to distribute incoming requests across multiple EC2 instances running your Node.js application. Here's a simplified configuration: + +1. Set up multiple EC2 instances with your Node.js app. + +2. Create an Elastic Load Balancer and attach your instances to it. + +3. Configure the load balancer to evenly distribute incoming requests. + +With this setup, your application can handle a higher number of concurrent users during peak times. + +Remember that the choice of deployment and scaling strategy depends on your application's specific requirements and budget. It's essential to monitor your application's performance and adjust your scaling strategy accordingly to ensure optimal performance and availability. + +## Chapter 10: Keeping Up with Node.js + +The world of technology is ever-evolving. In this final chapter, we'll provide you with resources and tips to stay up-to-date with Node.js trends and best practices. Continuous learning is the key to mastering Node.js. + +### **Node.js Community** + +Node.js has a vibrant and active community of developers worldwide. Engaging with this community is an excellent way to stay informed about the latest developments. You can do this by: + +1. **Participating in Online Forums:** Platforms like Stack Overflow and the Node.js community forums are great places to ask questions, share your knowledge, and learn from others. + +2. **Joining Social Media:** Follow Node.js-related accounts on Twitter, Reddit, and LinkedIn. These platforms often feature discussions, news, and announcements about Node.js. + +### **Blogs and Newsletters** + +Several blogs and newsletters are dedicated to Node.js updates and tutorials. Subscribing to these resources will keep you informed and inspired. Some popular options include: + +1. **Node.js Official Blog:** The official Node.js blog provides insights into the latest releases, project updates, and best practices. + +2. **The Node.js Collection on Medium:** Medium hosts a collection of articles on Node.js-related topics, written by experts and enthusiasts. + +3. **Node Weekly:** A weekly newsletter that curates the most important news, articles, and tutorials related to Node.js. + +### **Conferences and Meetups** + +Attending Node.js conferences and local meetups can be an enriching experience. Here, you can: + +1. **Network:** Connect with fellow Node.js developers, share experiences, and even collaborate on projects. + +2. **Learn from Experts:** Conferences often feature talks and workshops by experts in the field. You can gain valuable insights and even discover emerging trends. + +### **Continuous Learning** + +Node.js, like any technology, evolves. To stay at the forefront, consider taking online courses or tutorials on platforms like Udemy, Coursera, or Pluralsight. These resources offer courses on advanced Node.js topics, such as microservices, serverless computing, and real-time applications. + +Let's say you want to keep up with the latest developments in Node.js by following the Node.js Twitter community. You can start by creating a Twitter account if you don't already have one. Then, search for relevant hashtags like #Nodejs or #JavaScript. Follow influential Node.js developers, organizations, and official accounts. + +As you scroll through your Twitter feed, you'll come across tweets about Node.js updates, new libraries, and interesting articles. Engage in conversations, ask questions, and share your thoughts. This active participation will not only help you stay informed but also expand your network within the Node.js community. + +Remember, staying current in the world of Node.js is an ongoing process. By actively participating in the community, consuming relevant content, and continuously learning, you'll be well-equipped to master Node.js and build wonderful and useful applications in this dynamic ecosystem. + +### **Conclusion** + +Congratulations! You've now completed our beginner's guide to mastering Node.js. You've gained a solid foundation in Node.js development, and you're well on your way to building wonderful and useful tools and applications. Remember, the journey of a developer is never-ending, so keep coding, keep learning, and keep pushing the boundaries of what you can achieve with Node.js. Happy coding! \ No newline at end of file diff --git a/contentlayer.config.js b/contentlayer.config.js new file mode 100644 index 0000000..f3c0453 --- /dev/null +++ b/contentlayer.config.js @@ -0,0 +1,54 @@ +import { defineDocumentType, makeSource } from "contentlayer/source-files"; + +/** @type {import('contentlayer/source-files').ComputedFields} */ +const computedFields = { + slug: { + type: "string", + resolve: (doc) => `/${doc._raw.flattenedPath}`, + }, + slugAsParams: { + type: "string", + resolve: (doc) => doc._raw.flattenedPath.split("/").slice(1).join("/"), + }, +}; + +export const Page = defineDocumentType(() => ({ + name: "Page", + filePathPattern: "pages/**/*.mdx", + contentType: "mdx", + fields: { + title: { + type: "string", + required: true, + }, + description: { + type: "string", + }, + }, + computedFields, +})); + +export const Post = defineDocumentType(() => ({ + name: "Post", + filePathPattern: "posts/**/*.mdx", + contentType: "mdx", + fields: { + title: { + type: "string", + required: true, + }, + description: { + type: "string", + }, + date: { + type: "date", + required: true, + }, + }, + computedFields, +})); + +export default makeSource({ + contentDirPath: "./content", + documentTypes: [Post, Page], +}); diff --git a/lib/config.ts b/lib/config.ts new file mode 100644 index 0000000..9e2efb0 --- /dev/null +++ b/lib/config.ts @@ -0,0 +1,13 @@ +import config from "@/assets/config.json"; + +export interface Config { + readonly base_url: string; + readonly site_title: string; + readonly site_description: string; + readonly site_keywords: string[]; + readonly posts_per_page: number; + readonly twitter_account: string; + readonly github_account: string; +} + +export default config as Config; diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 0000000..bd0c391 --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/next.config.js b/next.config.js new file mode 100644 index 0000000..62522c4 --- /dev/null +++ b/next.config.js @@ -0,0 +1,14 @@ +const { withContentlayer } = require("next-contentlayer"); + +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, + swcMinify: true, + images: { + remotePatterns: [{ hostname: "api.microlink.io" }], + unoptimized: true, + }, + output: "export", +}; + +module.exports = withContentlayer(nextConfig); diff --git a/package.json b/package.json index 6f9e820..001f9bf 100644 --- a/package.json +++ b/package.json @@ -1,38 +1,43 @@ { - "name": "portfolio-astro", - "type": "module", - "version": "0.0.1", + "name": "next-contentlayer", + "version": "0.1.0", + "private": true, "scripts": { - "dev": "astro dev", - "start": "astro dev", - "build": "astro check && astro build", - "preview": "astro preview", - "astro": "astro" + "dev": "next dev", + "build": "next build", + "preview": "next build && next start", + "start": "next start", + "lint": "next lint", + "export": "next build && next export" }, "dependencies": { - "@astrojs/check": "^0.9.4", - "@astrojs/mdx": "^3.1.8", - "@astrojs/react": "^3.6.2", - "@astrojs/rss": "^4.0.9", - "@astrojs/sitemap": "^3.2.1", - "@astrojs/tailwind": "^5.1.2", + "@radix-ui/react-hover-card": "^1.1.1", "@radix-ui/react-icons": "^1.3.0", - "@radix-ui/react-slot": "^1.1.0", - "@tailwindcss/typography": "^0.5.15", - "@types/react": "^18.3.11", - "@types/react-dom": "^18.3.1", - "astro": "^4.16.5", + "@radix-ui/react-separator": "^1.1.0", + "@tailwindcss/typography": "^0.5.9", + "@vercel/analytics": "^1.0.0", + "autoprefixer": "^10.4.14", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", - "lucide-react": "^0.453.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "tailwind-merge": "^2.5.4", - "tailwindcss": "^3.4.14", - "tailwindcss-animate": "^1.0.7", - "typescript": "^5.6.3" + "contentlayer": "^0.3.2", + "framer-motion": "^11.5.4", + "lucide-react": "^0.439.0", + "next": "^14.2.7", + "next-contentlayer": "^0.3.2", + "next-themes": "^0.2.1", + "postcss": "^8.4.23", + "qss": "^3.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "tailwind-merge": "^2.5.2", + "tailwindcss": "^3.3.2", + "tailwindcss-animate": "^1.0.7" }, "devDependencies": { - "@biomejs/biome": "1.9.3" + "@biomejs/biome": "1.8.3", + "@types/node": "^22.5.4", + "@types/react": "^18.3.5", + "@types/react-dom": "^18.3.0", + "typescript": "^5.5.4" } } diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..33ad091 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/public/android-chrome-192x192.png b/public/android-chrome-192x192.png new file mode 100644 index 0000000..cbf4871 Binary files /dev/null and b/public/android-chrome-192x192.png differ diff --git a/public/android-chrome-512x512.png b/public/android-chrome-512x512.png new file mode 100644 index 0000000..e077437 Binary files /dev/null and b/public/android-chrome-512x512.png differ diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000..f248b99 Binary files /dev/null and b/public/apple-touch-icon.png differ diff --git a/public/blog-post-1.jpg b/public/blog-post-1.jpg new file mode 100644 index 0000000..0cdc7d3 Binary files /dev/null and b/public/blog-post-1.jpg differ diff --git a/public/blog-post-2.jpg b/public/blog-post-2.jpg new file mode 100644 index 0000000..164d589 Binary files /dev/null and b/public/blog-post-2.jpg differ diff --git a/public/blog-post-3.jpg b/public/blog-post-3.jpg new file mode 100644 index 0000000..798d8c8 Binary files /dev/null and b/public/blog-post-3.jpg differ diff --git a/public/blog-post-4.jpg b/public/blog-post-4.jpg new file mode 100644 index 0000000..b17510c Binary files /dev/null and b/public/blog-post-4.jpg differ diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png new file mode 100644 index 0000000..053647a Binary files /dev/null and b/public/favicon-16x16.png differ diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png new file mode 100644 index 0000000..a6e1087 Binary files /dev/null and b/public/favicon-32x32.png differ diff --git a/public/favicon.svg b/public/favicon.svg deleted file mode 100644 index f157bd1..0000000 --- a/public/favicon.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - diff --git a/public/fonts/Chocolate.ttf b/public/fonts/Chocolate.ttf deleted file mode 100644 index 65d532b..0000000 Binary files a/public/fonts/Chocolate.ttf and /dev/null differ diff --git a/public/header.ttf b/public/header.ttf new file mode 100644 index 0000000..a41375f Binary files /dev/null and b/public/header.ttf differ diff --git a/public/icon.ico b/public/icon.ico deleted file mode 100644 index f03bd8f..0000000 Binary files a/public/icon.ico and /dev/null differ diff --git a/public/images/Screenshot-circle.png b/public/images/Screenshot-circle.png deleted file mode 100644 index 7a16cef..0000000 Binary files a/public/images/Screenshot-circle.png and /dev/null differ diff --git a/public/images/Screenshot-html.png b/public/images/Screenshot-html.png deleted file mode 100644 index 5efb84b..0000000 Binary files a/public/images/Screenshot-html.png and /dev/null differ diff --git a/public/images/blog-placeholder-about.jpg b/public/images/blog-placeholder-about.jpg deleted file mode 100644 index cf5f685..0000000 Binary files a/public/images/blog-placeholder-about.jpg and /dev/null differ diff --git a/public/images/gradient-1.webp b/public/images/gradient-1.webp deleted file mode 100644 index 7a88513..0000000 Binary files a/public/images/gradient-1.webp and /dev/null differ diff --git a/public/images/grumpy-cat-small.png b/public/images/grumpy-cat-small.png deleted file mode 100644 index cdb49d2..0000000 Binary files a/public/images/grumpy-cat-small.png and /dev/null differ diff --git a/public/images/image.webp b/public/images/image.webp deleted file mode 100644 index b5afecf..0000000 Binary files a/public/images/image.webp and /dev/null differ diff --git a/public/next.svg b/public/next.svg new file mode 100644 index 0000000..5174b28 --- /dev/null +++ b/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/og_image.png b/public/og_image.png new file mode 100644 index 0000000..00a900f Binary files /dev/null and b/public/og_image.png differ diff --git a/public/site.webmanifest b/public/site.webmanifest new file mode 100644 index 0000000..389d7a5 --- /dev/null +++ b/public/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "Owenstack portfolio and blog", + "short_name": "Owenstack", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#2F27CE", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/public/vercel.svg b/public/vercel.svg new file mode 100644 index 0000000..d2f8422 --- /dev/null +++ b/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/BaseHead.astro b/src/components/BaseHead.astro deleted file mode 100644 index d26b8c6..0000000 --- a/src/components/BaseHead.astro +++ /dev/null @@ -1,44 +0,0 @@ ---- -// Import the global.css file here so that it is included on -// all pages through the use of the component. -import "@/styles/global.css"; - -interface Props { - title: string; - description: string; - image?: string; -} - -const canonicalURL = new URL(Astro.url.pathname, Astro.site); - -const { title, description, image = "/blog-placeholder-1.jpg" } = Astro.props; ---- - - - - - - - - - - - -{title} - - - - - - - - - - - - - - - - diff --git a/src/components/Footer.astro b/src/components/Footer.astro deleted file mode 100644 index 3be3c4a..0000000 --- a/src/components/Footer.astro +++ /dev/null @@ -1,21 +0,0 @@ ---- -import { ThemeToggle } from "./theme-toggle"; -const today = new Date(); -import Gmail from "@/components/icons/google"; -import X from "@/components/icons/twitter"; ---- - -
-
- © {today.getFullYear()} Owen. All rights reserved. -
- - -
diff --git a/src/components/FormattedDate.astro b/src/components/FormattedDate.astro deleted file mode 100644 index 1bcce73..0000000 --- a/src/components/FormattedDate.astro +++ /dev/null @@ -1,17 +0,0 @@ ---- -interface Props { - date: Date; -} - -const { date } = Astro.props; ---- - - diff --git a/src/components/Header.astro b/src/components/Header.astro deleted file mode 100644 index 5058c66..0000000 --- a/src/components/Header.astro +++ /dev/null @@ -1,42 +0,0 @@ ---- -import { SITE_TITLE } from "@/consts"; -import HeaderLink from "./HeaderLink.astro"; ---- - - - -
- -
diff --git a/src/components/HeaderLink.astro b/src/components/HeaderLink.astro deleted file mode 100644 index aea33b7..0000000 --- a/src/components/HeaderLink.astro +++ /dev/null @@ -1,17 +0,0 @@ ---- -import { Button } from "@/components/ui/button"; -import type { HTMLAttributes } from "astro/types"; -type Props = HTMLAttributes<"a">; - -const { href, class: className, ...props } = Astro.props; - -const { pathname } = Astro.url; -const subpath = pathname.match(/[^\/]+/g); -const isActive = href === pathname || href === `/${subpath?.[0]}`; ---- - - diff --git a/src/components/icons/O-wen.svg b/src/components/icons/O-wen.svg deleted file mode 100644 index 5bc012b..0000000 --- a/src/components/icons/O-wen.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/components/icons/Vector 2.svg b/src/components/icons/Vector 2.svg deleted file mode 100644 index 174211b..0000000 --- a/src/components/icons/Vector 2.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/components/icons/Vector 3.svg b/src/components/icons/Vector 3.svg deleted file mode 100644 index 7f13f72..0000000 --- a/src/components/icons/Vector 3.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/components/icons/Vector 4.svg b/src/components/icons/Vector 4.svg deleted file mode 100644 index fc48ea4..0000000 --- a/src/components/icons/Vector 4.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/components/icons/o-Wen.svg b/src/components/icons/o-Wen.svg deleted file mode 100644 index fcf151b..0000000 --- a/src/components/icons/o-Wen.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/components/icons/ow-En.svg b/src/components/icons/ow-En.svg deleted file mode 100644 index 867712d..0000000 --- a/src/components/icons/ow-En.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/components/icons/owe-N.svg b/src/components/icons/owe-N.svg deleted file mode 100644 index d1993d2..0000000 --- a/src/components/icons/owe-N.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/components/theme-toggle.tsx b/src/components/theme-toggle.tsx deleted file mode 100644 index 3202af4..0000000 --- a/src/components/theme-toggle.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { Monitor, Moon, Sun } from "lucide-react"; -import { useEffect, useState } from "react"; -import { Button } from "./ui/button"; - -export function ThemeToggle() { - const [theme, setTheme] = useState<"light" | "dark" | "system">(() => { - if (typeof window !== "undefined" && window.localStorage) { - const storedTheme = window.localStorage.getItem("theme") as - | "light" - | "dark" - | "system" - | null; - if (storedTheme) { - return storedTheme; - } - } - return "system"; - }); - - useEffect(() => { - const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); - const handleChange = () => { - if (theme === "system") { - updateTheme(theme); - } - }; - - mediaQuery.addEventListener("change", handleChange); - return () => mediaQuery.removeEventListener("change", handleChange); - }, [theme]); - - useEffect(() => { - updateTheme(theme); - }, [theme]); - - const updateTheme = (newTheme: "light" | "dark" | "system") => { - const isDark = - newTheme === "dark" || - (newTheme === "system" && - window.matchMedia("(prefers-color-scheme: dark)").matches); - - document.documentElement.classList.toggle("dark", isDark); - if (typeof window !== "undefined" && window.localStorage) { - window.localStorage.setItem("theme", newTheme); - } - }; - - return ( -
-
- Toggle theme - - - -
-
- ); -} diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx deleted file mode 100644 index 2fba20e..0000000 --- a/src/components/ui/button.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { Slot } from "@radix-ui/react-slot"; -import { type VariantProps, cva } from "class-variance-authority"; -import * as React from "react"; - -import { cn } from "@/lib/utils"; - -const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", - { - variants: { - variant: { - default: - "bg-primary text-primary-foreground shadow hover:bg-primary/90", - destructive: - "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", - outline: - "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", - secondary: - "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", - ghost: "hover:bg-accent hover:text-accent-foreground", - link: "text-primary underline-offset-4 hover:underline", - }, - size: { - default: "h-9 px-4 py-2", - sm: "h-8 rounded-md px-3 text-xs", - lg: "h-10 rounded-md px-8", - icon: "h-9 w-9", - }, - }, - defaultVariants: { - variant: "default", - size: "default", - }, - }, -); - -export interface ButtonProps - extends React.ButtonHTMLAttributes, - VariantProps { - asChild?: boolean; -} - -const Button = React.forwardRef( - ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button"; - return ( - - ); - }, -); -Button.displayName = "Button"; - -export { Button, buttonVariants }; diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx deleted file mode 100644 index 77e9fb7..0000000 --- a/src/components/ui/card.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import * as React from "react" - -import { cn } from "@/lib/utils" - -const Card = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) -Card.displayName = "Card" - -const CardHeader = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) -CardHeader.displayName = "CardHeader" - -const CardTitle = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -

-)) -CardTitle.displayName = "CardTitle" - -const CardDescription = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -

-)) -CardDescription.displayName = "CardDescription" - -const CardContent = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -

-)) -CardContent.displayName = "CardContent" - -const CardFooter = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) -CardFooter.displayName = "CardFooter" - -export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/src/consts.ts b/src/consts.ts deleted file mode 100644 index 5248643..0000000 --- a/src/consts.ts +++ /dev/null @@ -1,5 +0,0 @@ -// Place any global data in this file. -// You can import this data from anywhere in your site by using the `import` keyword. - -export const SITE_TITLE = "Owenstack"; -export const SITE_DESCRIPTION = "Full Stack web developer portfolio and blog"; diff --git a/src/content/blog/introduction-data-science.md b/src/content/blog/introduction-data-science.md deleted file mode 100644 index 7e44c0d..0000000 --- a/src/content/blog/introduction-data-science.md +++ /dev/null @@ -1,124 +0,0 @@ ---- -title: 'Introduction to Data in Data Analysis' -description: 'A comprehensive introduction to data for data analysis' -pubDate: 'Nov 18 2024' -heroImage: '/images/gradient-1.webp' ---- - -### **Lesson Plan: Introduction to Data for Data Analysis** - -#### **Lesson Objective** -By the end of the session, learners will: -- Understand what data is and its importance. -- Learn the types and sources of data. -- Grasp the basics of data collection, cleaning, and analysis. -- Be introduced to the role of data in business insights and decision-making. - ---- - -### **Lesson Outline** - -#### **Understanding Data** -1. **What is Data?** - - Definition of data: Data refers to raw facts and figures that can be processed to extract meaningful information. It can be structured (organized in tables) or unstructured (like text or images). - - Data vs. Information: The transformation of data into meaningful insights. [Data vs Information](https://bloomfire.com/blog/data-vs-information/) - - Examples of data in everyday life (e.g., weather reports, social media metrics). - -2. **Types of Data** - - Qualitative vs. Quantitative Data. - - Structured vs. Unstructured Data. - - Examples: - - Qualitative: Customer reviews, survey responses. - - Quantitative: Sales figures, test scores. - - Reference(s): [Types of Data](https://www.coursera.org/articles/types-of-data), [Types of Data](https://www.coursera.org/in/articles/types-of-data), [Unstructured data and structured data](https://www.questionpro.com/blog/structured-data/) - -3. **Sources of Data** - - Internal vs. External Sources: - - Internal: Company databases, employee records. - - External: Market research, social media analytics. - - Primary vs. Secondary Data: - - Primary: Surveys, interviews, experiments. - - Secondary: Articles, government reports, public datasets. - - Reference(s): [Sources of Data](https://www.questionpro.com/blog/sources-of-data/), [What are data sources](https://www.akooda.co/blog/what-are-data-sources) - - **Activity**: Brainstorm sources of data learners might encounter in daily life. - ---- - -#### **Why Data Matters** -1. **The Role of Data in Modern Business** - - How data drives decision-making (examples: pricing strategies, market trends). - - Real-world examples of data-driven companies (e.g., Amazon, Google). - - Reference(s): [Advantages of data driven decision making by Harvard Business School](https://online.hbs.edu/blog/post/data-driven-decision-making) - -2. **Applications of Data Analysis** - - Understanding customer behavior. - - Improving operational efficiency. - - Forecasting and risk management. - -3. **Benefits of Data-Driven Decision-Making** - - Accuracy and precision in insights. - - Competitive advantage in the market. - - Cost reduction and profitability. - - Reference(s): [Data driven decision making by Asana](https://asana.com/resources/data-driven-decision-making), [A guide to data driven decision making by Tableau](https://www.tableau.com/learn/articles/data-driven-decision-making), - - **Discussion**: Learners share examples of how businesses around them use data. - ---- - -#### **Basics of Data Collection and Cleaning** -1. **Data Collection Techniques** - - Surveys and Questionnaires. - - Observations and Experiments. - - Web scraping and API integration (brief mention). - - Reference(s): [PiggyVest Savings Report 2023](https://www.piggyvest.com/reports/2023), [PiggyVest Savings Report 2024](https://www.piggyvest.com/reports/2024), [Data Collection by Simplilearn](https://www.simplilearn.com/what-is-data-collection-article), [Data Collection Techniques](https://safetyculture.com/topics/data-collection/data-collection-techniques/) - -2. **Introduction to Data Cleaning** - - Definition: Removing errors, duplicates, and inconsistencies. - - Importance of clean data for accurate analysis. - - Examples of errors: Typos, missing values, outliers. - - Reference(s): [Data Cleansing by Wikipedia](https://en.wikipedia.org/wiki/Data_cleansing), [What is data cleansing by Career Foundry](https://careerfoundry.com/en/blog/data-analytics/what-is-data-cleaning/), [Data Cleaning by Career Foundry](https://careerfoundry.com/en/blog/data-analytics/what-is-data-cleaning/) - - **Activity**: Display a messy dataset and ask learners to identify issues. - ---- - -#### **Data in Action** -1. **Exploratory Data Analysis (EDA)** - - What is EDA? Gaining insights from raw data. - - Overview of basic techniques: - - Descriptive statistics: Mean, median, mode. - - Visualizations: Bar charts, line graphs, scatter plots. - - Walkthrough of a simple dataset in Excel or Google Sheets. - - Reference(s): [Exploratory data analysis by IBM](https://www.ibm.com/topics/exploratory-data-analysis), [Exploratory data analysis by Career Foundry](https://careerfoundry.com/en/blog/data-analytics/exploratory-data-analysis/) - -2. **Case Study: Data-Driven Insights** - - Present a real-life business scenario: - - Example: Open Discussion on ways DDI can be applied in students' current workplace, UNN ICT - - **Activity**: Learners practice drawing insights from a simplified dataset. - -3. **Future Steps in Data Analysis** - - Overview of advanced concepts: - - Predictive analytics. - - Machine learning. - - Importance of continuous learning and practice. - - Reference(s): [Predictive analysis by Harvard Business School](https://online.hbs.edu/blog/post/predictive-analytics), [Predictive analysis by Investopedia](https://www.investopedia.com/terms/p/predictive-analytics.asp), [Predictive analysis by IBM](https://www.ibm.com/topics/predictive-analytics), 5 - ---- - -### **Assessment** -- Questions throughout the session to gauge understanding. -- Small group activity where learners summarize the session's key points. -- Optional: Provide a simple dataset for learners to analyze after class. - -### **Homework** -- Research and note down 3 examples of how data is used in businesses or industries learners are interested in. -- Explore and clean a small dataset using Excel or Google Sheets. diff --git a/src/content/blog/introduction.md b/src/content/blog/introduction.md deleted file mode 100644 index 570577a..0000000 --- a/src/content/blog/introduction.md +++ /dev/null @@ -1,119 +0,0 @@ ---- -title: 'Introduction to Web Development' -description: 'A brief overview on HTML' -pubDate: 'Jun 19 2024' -heroImage: '/images/Screenshot-html.png' ---- - -Hey everyone! Welcome to the start of what’s going to be an epic web development journey. We don’t have the entire map drawn out yet, but that’s the exciting part. Let’s take this first step together and explore the world of web development. Who knows where we’ll end up? Ready? Let’s go! - -## Course Material(s) -- [MDN Web Docs](https://developer.mozilla.org/en-US/) - -## Web Development Basics: What’s the Big Deal? -Let’s keep things simple: HTML, CSS, and JavaScript are the core trio that brings the web to life. Together, they form the foundation of what we see and interact with online. Today, we’ll scratch the surface of where they came from and why they matter. - -### The Web: Your Personal Info Delivery Service -The web’s like the ultimate middleman—making sure info gets from one place to another. Here’s the flow: - -1. You (the user) start the process by making a request in your browser. -2. Your browser sends a "GET" request for the page you want to visit. -3. This goes to the DNS (Domain Name System), which translates the URL into the IP address of the server. -4. The server, if it’s got what you need, sends back a response. -5. The browser grabs that response and boom—you see the content on your screen. - -### HTML: The Skeleton of the Web -HTML, short for **Hypertext Markup Language**, is your web’s backbone. Let’s break it down: - -- **Hypertext**: Linking between pages (basically, you click and jump to another page). -- **Markup**: A way to describe content (think of it as a label system). -- **Language**: A set of rules that everyone (i.e., browsers) understands. - -If we think of a webpage as a house, HTML is the structure that holds everything up. But a house with just a frame? Not super fun to live in. You’ll need some style—that’s where CSS comes in. - -HTML elements are the building blocks, like ``, ``, ``, and others. They tell the browser how to structure the content. - -Here's a basic HTML element example: - -![Anatomy of an HTML element](/images/grumpy-cat-small.png) - -Elements can also have attributes, giving them extra capabilities. Some important ones include: - -- `src`: Where your content (like an image) is coming from. -- `class`: A way to group elements for styling. -- `id`: A unique identifier for elements. - -Now, here’s a basic HTML document structure: - -```html - - - - - - My First Web Page - - -

Hello World

- - -``` - -A quick rundown of the parts: -- ``: Tells the browser we’re working with HTML5. -- ``: The root of the document. -- ``: Contains meta info (like encoding and title). -- ``: The content that’s visible on the webpage. - -### CSS: The Stylist of the Web -If HTML is the skeleton, CSS is the stylish wardrobe that makes everything look good. CSS stands for **Cascading Style Sheets** and is only limited by your imagination. We’ll dive deeper into CSS in the next session. - -## Putting It Into Practice -So, with what we’ve learned so far, here's what the students came up with: - -```html - - - - - - Learning HTML - - - -
- Shapes -
-
-

About the Web Dev Class

-

Attendees:

-
    -
  1. Mmesoma
  2. -
  3. Abraham
  4. -
  5. Mercy
  6. -
  7. Philip
  8. -
-

Otherwise known as:

-
    -
  • Esther
  • -
  • AB
  • -
  • Chijioke's best friend
  • -
  • Big smoke
  • -
-
- - -``` - -## Wrapping Up -By the end of today’s session, we had something that looked like this: - -![Student work](/images/Screenshot-html.png) - -Before we meet again, your homework is to sign up for [Free Code Camp](https://www.freecodecamp.org/) and finish the first chapter. It’s an awesome way to practice what you’re learning. - -Remember, web development is an art form. Your tools—HTML, CSS, and JavaScript—are the brushes, and the web is your canvas. With enough practice, you can create just about anything you dream up. - -Stay curious, keep building, and I’ll see you in the next lesson where we’ll explore the magic of CSS! - -*Image credit: Anatomy of an HTML element courtesy of [MDN](https://developer.mozilla.org)* diff --git a/src/content/blog/transition.md b/src/content/blog/transition.md deleted file mode 100644 index eb81c2e..0000000 --- a/src/content/blog/transition.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -title: 'From NextJs to Remix and Cloudflare Workers' -description: 'We moved a project from NextJs to Remix basically for cost savings' -pubDate: 'Mar 12 2025' -heroImage: '/images/image.webp' ---- - -#### Introduction -In a bid to optimize performance, reduce costs, and simplify development workflows, we have successfully transitioned from Next.js to Remix, deploying on Cloudflare Workers. Initially, our project leveraged Next.js on Vercel, complemented by AWS backend services. While this setup provided a solid foundation, our evolving needs necessitated a more efficient and cost-effective solution. - -Remix's server-side rendering (SSR) model, seamless integration with Cloudflare's edge network, and robust resource bindings have proven to be significant advantages over our previous architecture. This document outlines the key motivations behind our transition and details the improvements in performance, operational efficiencies, and cost savings that Remix and Cloudflare Workers have delivered. - -#### 1. Remix’s Server-Side Rendering (SSR): A Performance and Simplicity Boost -Next.js offered multiple rendering strategies—Static Site Generation (SSG), Incremental Static Regeneration (ISR), Server-Side Rendering (SSR), and Client-Side Rendering (CSR). While this flexibility was useful, it introduced complexity in choosing the right approach for each page. Remix, on the other hand, is designed around SSR by default, simplifying data fetching and ensuring a more consistent performance profile. - -- **Why Remix SSR Excels**: Remix utilizes the Web Fetch API to execute server-side logic efficiently, delivering pre-rendered pages to the client with minimal JavaScript overhead. This approach results in faster initial page loads and better SEO. In contrast, Next.js required explicit SSR configuration (e.g., `getServerSideProps`) and often involved client-side fetching trade-offs. -- **Performance Gains**: Deploying Remix on Cloudflare Workers has allowed for true edge rendering, eliminating the latency associated with Node.js-based SSR solutions like Vercel. By rendering content at the edge, closer to users, we have achieved lower Time to First Byte (TTFB) and improved global performance. -- **Developer Experience**: Remix’s approach to data fetching and mutations via loaders and actions has streamlined our development process. Unlike Next.js, where multiple rendering strategies and client-side data-fetching tools (e.g., SWR, React Query) were often required, Remix centralizes data handling on the server, making development more intuitive and maintainable. - -**Impact**: Remix’s SSR model has simplified our architecture, improved performance by reducing client-side overhead, and ensured consistently fast content delivery when paired with Cloudflare Workers. - -#### 2. Ease of Use of Cloudflare Bindings for Resource Access -Cloudflare Workers provides a powerful ecosystem of bindings—such as KV storage, Durable Objects, and Queues—that integrate seamlessly with Remix, reducing the need for traditional backend infrastructure. - -- **Simplified Resource Management**: In our previous Next.js setup, accessing external resources (databases, caches, etc.) required API routes and additional middleware, increasing complexity. With Remix on Cloudflare Workers, we now have direct and efficient access to these resources via Cloudflare bindings. -- **Type Safety and Configuration**: Remix’s integration with Cloudflare Workers allows for automatic TypeScript type generation for bindings, reducing errors and improving development speed. -- **Scalability**: Cloudflare’s edge infrastructure automatically scales without the operational overhead of managing server instances, making it an ideal fit for applications expecting unpredictable traffic spikes. - -**Impact**: Cloudflare bindings have simplified our backend logic, improved scalability, and enhanced developer productivity with type-safe, serverless resource management. - -#### 3. General Cost Reduction -One of the most compelling outcomes of our transition to Remix and Cloudflare Workers is the significant cost savings compared to our previous Next.js + Vercel + AWS setup. - -- **Previous Costs with Next.js on Vercel**: Hosting with Vercel incurred costs based on bandwidth, build times, and serverless function invocations. As our application scaled, these costs became substantial, with static asset hosting alone often exceeding $40/month, plus additional charges for serverless functions and database queries. -- **Cloudflare Workers Pricing**: Cloudflare Workers offers a generous free tier (100,000 requests/day) and a low pay-as-you-go pricing model ($0.30 per million requests). Features such as DDoS protection, CDN, and caching are included at no extra cost, making it a highly cost-effective solution compared to AWS and Vercel. -- **Build and Deployment Savings**: Remix’s streamlined build process has reduced CI/CD overhead compared to Next.js, which required managing SSG/ISR builds and could become resource-intensive. - -**Impact**: Moving to Remix and Cloudflare Workers has significantly reduced our hosting and infrastructure costs while providing enterprise-grade capabilities at a fraction of the price. - -#### 4. Additional Benefits -Beyond performance, scalability, and cost savings, this transition has provided additional strategic advantages: - -- **Edge-Native Deployment**: Cloudflare Workers deploy code across over 250 global edge locations, ensuring low-latency content delivery. -- **Progressive Enhancement**: Remix’s emphasis on web standards (e.g., native form handling, robust error handling) makes applications more resilient and accessible, even when JavaScript is disabled. -- **Improved UX**: Built-in error boundaries and scroll restoration have improved user experience without requiring additional configuration, unlike Next.js. -- **Reduced Complexity**: While Remix’s ecosystem is smaller than Next.js’s, its adherence to web standards and simpler architecture have minimized our reliance on third-party libraries, reducing long-term maintenance costs. - -**Impact**: The shift to Remix and Cloudflare Workers has aligned with best practices for performance, accessibility, and maintainability, making it a future-proof choice. - -#### Conclusion -The transition from Next.js to Remix, coupled with deployment on Cloudflare Workers, has provided clear advantages in performance, cost efficiency, and developer experience. Remix’s server-side rendering model has simplified our architecture while delivering faster page loads, Cloudflare bindings have provided a seamless and scalable backend solution, and Cloudflare Workers have dramatically reduced infrastructure costs. - -Having completed this transition, we are now able to take full advantage of edge computing, improved developer experience, and reduced complexity, ensuring that our application remains highly performant and cost-efficient for the long term. - diff --git a/src/content/config.ts b/src/content/config.ts deleted file mode 100644 index 8917b31..0000000 --- a/src/content/config.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { defineCollection, z } from "astro:content"; - -const blog = defineCollection({ - type: "content", - // Type-check frontmatter using a schema - schema: z.object({ - title: z.string(), - description: z.string(), - // Transform string to Date object - pubDate: z.coerce.date(), - updatedDate: z.coerce.date().optional(), - heroImage: z.string().optional(), - }), -}); - -const project = defineCollection({ - type: "content", - // Type-check frontmatter using a schema - schema: z.object({ - title: z.string(), - description: z.string(), - pubDate: z.coerce.date(), - updatedDate: z.coerce.date().optional(), - heroImage: z.string().optional(), - }), -}); - -export const collections = { blog, project }; diff --git a/src/content/drafts/data-analytics.md b/src/content/drafts/data-analytics.md deleted file mode 100644 index 659d1d9..0000000 --- a/src/content/drafts/data-analytics.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: 'Introduction to Data in Data Analysis' -description: 'A comprehensive introduction to data for data analysis' -pubDate: 'Nov 18 2024' -heroImage: '/images/gradient-1.webp' ---- - -Understand the problem -Data collection -Data cleaning - incomplete, duplicate -Data analysis -Draw your conclusion - validation check - -Python - oop, visualization, manipulation, {numpy {numerical computation of data}, scipy {technical and scientific computations}, pandas {manipulate data,time series}} -R - data analysis and data visualization {ggplot, plotlb, deployr} -Tableau - reports and dashboards -Power BI - same as Tableau -Apache Spark - process data in real time, and sql query, spark stream and spark sql -Sas - Statistical analysis software that analyze and visualize data -QlikView - interactive analytics with in memory storage - -Retail -what they need, when they need, supply chain opimization -Healthcare -insurance, new drugs development, disease discovery -Manufacturing - new cost saving and revenue upstream -Banking - -Logistics - optimize routes, performance management - -Amazon -Accenture -Cigna -Cerner -Target -McAfee -Rapido -FlipKart -Walmart - -linear regression in r - diff --git a/src/env.d.ts b/src/env.d.ts deleted file mode 100644 index e16c13c..0000000 --- a/src/env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/src/layouts/BlogPost.astro b/src/layouts/BlogPost.astro deleted file mode 100644 index c4194f8..0000000 --- a/src/layouts/BlogPost.astro +++ /dev/null @@ -1,46 +0,0 @@ ---- -import type { CollectionEntry } from "astro:content"; -import BaseHead from "@/components/BaseHead.astro"; -import Footer from "@/components/Footer.astro"; -import FormattedDate from "@/components/FormattedDate.astro"; -import Header from "@/components/Header.astro"; - -type Props = CollectionEntry<"blog">["data"]; - -const { title, description, pubDate, updatedDate, heroImage } = Astro.props; ---- - - - - - - - -
-
-
- {heroImage && ( -
- {title} -
- )} -
-
- - {updatedDate && ( - - (Updated: ) - - )} -
-

{title}

-
-
-
- -
-
-
-