2026-10-05
In JavaScript, variables can be declared using let, const, or var.
let: Used to declare variables that can be reassigned.
const: Used to declare variables that cannot be reassigned.
var: An older way to declare variables, generally not recommended due to its function-scoped behavior.
Examples:
// Using let for a variable that can be reassigned
let productName = "Laptop";
productName = "Smartphone"; // Reassigning the variable
// Using const for a variable that cannot be reassigned
const price = 999.99;
// price = 899.99; // This will cause an error
// Using var (not recommended)
var stock = 50;
stock = 30; // Reassigning the variablelet and const over var to avoid issues with variable scoping.const by default, and only use let if you need to reassign the variable.var to prevent unexpected behavior due to its function-scoped nature.String, Number, Boolean, Array, Object
Example:
Example:
Example:
Example:
Arrow functions provide a shorter syntax for writing function expressions in JavaScript.
Event listeners allow you to execute code in response to user interactions.
addEventListener method attaches a click event listener to the button with the ID helloButton.DOM manipulation allows you to change the content of HTML elements dynamically.
Example:
JavaScript uses prototypal inheritance, where objects inherit properties and methods from a prototype object.
ES6 introduced class syntax to simplify prototypal inheritance.
Example:
Example:
Displaying a List of Products computed on the client side:
<ul id="productList"></ul>
<script>
let products = ["Laptop", "Smartphone", "Tablet"];
let productList = document.getElementById("productList");
products.forEach(product => {
let listItem = document.createElement("li");
listItem.textContent = product;
productList.appendChild(listItem);
});
</script>With remix Stacks are essentially templates/starter kits that provide the groundwork for a complete project right out of the box.
For our project, we will use the Blues Stack, which gives us a fully configured Remix project with Prisma, Tailwind, and an entire module that shows how to use these tools to build a CRUD feature.
Step 0: Install Node.js and npm
To install both, download and install Node.js from the official website. npm comes bundled with Node.js.
Run the following command in your terminal:
This command will create a new folder named ama and set up the entire project after you answer yes to a couple of questions.
Prisma Directory: - Purpose: Manages database schema and migrations. - Contents: - schema.prisma: Defines your database schema. - Migration files: Track changes to your database schema over time.
Public Directory: - Purpose: Stores static assets. - Contents: - Icons, images, and other static files that your application needs.
App Directory: - Purpose: Contains the main application code. - Contents: - Routes: Define the different pages and API endpoints of your application. - Components: Reusable UI components. - Styles: Tailwind CSS configuration and custom styles. - Utils: Utility functions and helpers. - Data: Database access and data fetching logic.
.env file. If needed correct the database URL in the .env file: default is DATABASE_URL="postgresql://<USERNAME>:<PASSWORD>@localhost:5432/<DATABASE_NAME>" with your database credentials (postgres for both ?).DATABASE_URL="postgresql://<USERNAME>:<PASSWORD>@localhost:5432/<DATABASE_NAME>"Before starting the development server, you need to set up the project dependencies and environment variables.
Run the following command to install dependencies and set up the environment:
After setting up the project, you need to build it for the first time. This will compile the project and prepare it for development:
Finally, start the development server to run your project locally:
This will start the server, and you can view your project by opening a web browser and going to http://localhost:3000.
Open your web browser and navigate to:
You should see a Remix application to manage notes running locally.
By following these steps, you will have your development server up and running, allowing you to view and interact with your Remix project in real-time.
This starts your app in development mode, rebuilding assets on file changes.
app/routes directory.root.jsx in the project directory.// app/routes/dummyProducts.tsx
// Import necessary types and functions from Remix
import type { LoaderFunctionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
// Mock data for three products
const products = [
{ id: 1, name: 'Laptop', price: 999.99 },
{ id: 2, name: 'Smartphone', price: 499.99 },
{ id: 3, name: 'Tablet', price: 299.99 },
];
// Loader function to provide data to the component
// This function will be replaced with a database query in a real application
export const loader = async ({ request }: LoaderFunctionArgs) => {
return json(products); // Return the mock products data as JSON
};
// Default export function for the products page
export default function DummyProductsPage() {
const data = useLoaderData<typeof loader>(); // Use the loader data in the component
return (
<div className="min-h-screen bg-gray-100 p-4">
{/* Container for the content, centered and with padding */}
<div className="max-w-4xl mx-auto bg-white shadow-md rounded-lg p-6">
{/* Title of the page */}
<h1 className="text-3xl font-bold mb-4">Products</h1>
{/* List of products */}
<ul className="space-y-4">
{data.map(product => (
<li key={product.id} className="border-b pb-4">
{/* Product name */}
<div className="text-xl font-semibold">{product.name}</div>
{/* Product price */}
<div className="text-gray-600">${product.price}</div>
</li>
))}
</ul>
</div>
</div>
);
}root.tsx in the app/routes directory./). When a user navigates to the root URL, Remix will render the content defined in this file.root.tsx in the app/routes directory corresponds to the root URL (/).$ in Route$ symbol in the file name products/$productId.jsx indicates that productId is a dynamic segment. This means that any value in the URL that matches this segment will be captured as a parameter.
/products/1 or /products/2/products/1, the productId parameter will capture the value 1.loader function using params.
params.productId will give the value of productId from the URL.products/$productId.jsx can handle URLs like /products/1, /products/2, etc., and render the appropriate product details based on the productId parameter.This feature is useful for creating pages that need to display different content based on the URL, such as product details pages, user profiles, etc.
Explanation: In Remix, the $ symbol in a route file name indicates a dynamic segment. This allows the route to capture a part of the URL as a parameter.
Example: Create a file named products.$productId.tsx in theapp/routes` directory.
import { useLoaderData } from 'remix';
export let loader = async ({ params }) => {
let products = [
{ id: 1, name: 'Laptop', price: 999.99, description: 'A high-performance laptop.' },
{ id: 2, name: 'Smartphone', price: 499.99, description: 'A latest model smartphone.' },
];
return products.find(product => product.id === parseInt(params.productId));
};
export default function Product() {
let product = useLoaderData();
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>${product.price}</p>
</div>
);
}app/routes directory.
app/routes directory represents a route.File: app/routes/products/$productId.jsx
URL: /products/:productId
Example:
import { useLoaderData } from 'remix';
export let loader = async ({ params }) => {
let products = [
{ id: 1, name: 'Laptop', price: 999.99, description: 'A high-performance laptop.' },
{ id: 2, name: 'Smartphone', price: 499.99, description: 'A latest model smartphone.' },
];
return products.find(product => product.id === parseInt(params.productId));
};
export default function Product() {
let product = useLoaderData();
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>${product.price}</p>
</div>
);
}_ prefix in .tsx files typically indicates that the file is a layout route.Purpose: Handles form submissions.
Example (create a file named contact.jsx in the app/routes directory):
import { Form } from 'remix';
export default function Contact() {
return (
<div>
<h1>Contact Us</h1>
<Form method="post" action="/submit-contact">
<label>
Name:
<input type="text" name="name" />
</label>
<label>
Email:
<input type="email" name="email" />
</label>
<button type="submit">Submit</button>
</Form>
</div>
);
}Purpose: Provides client-side navigation between routes.
Example (create a file named welcome.jsx in the app/routes directory):
Explanation:
Link component is imported from Remix.Welcome component renders a heading and a navigation menu with links to the /products and /contact routes.Purpose: The NavLink component in Remix is similar to the Link component but provides additional styling for active links. This helps in indicating the current active route to the user.
Usage:
NavLink component is used to create navigational links that automatically apply an active class when the link’s route matches the current URL.Example:
Create a file named header.jsx in the app/components directory:
import { NavLink } from 'remix';
export default function Header() {
return (
<header>
<h1>My E-commerce Store</h1>
<nav>
<ul>
<li>
<NavLink to="/" end activeClassName="active">
Home
</NavLink>
</li>
<li>
<NavLink to="/products" activeClassName="active">
Products
</NavLink>
</li>
<li>
<NavLink to="/contact" activeClassName="active">
Contact
</NavLink>
</li>
</ul>
</nav>
</header>
);
}Explanation:
NavLink component is imported from Remix.Header component renders a heading and a navigation menu with NavLink components for the /, /products, and /contact routes.activeClassName="active" prop applies the active class to the link when its route matches the current URL.By using the NavLink component, you can create a more user-friendly navigation experience in your Remix application.
High-Level Summary:
model <EntityName> {}.{}, you define all the columns/properties of the entity and relationships with other entities.model Comment {}.<columnName> <columnType> <default/relationship/other specifiers>.Defines a product entity with columns for id, name, price, and description.
install the prisma vscode extension.
add entity Product to the prisma/schema.prisma file:
Defines a user entity with columns for id, name, email, and a relationship to the orders placed by the user.
Defines an order entity with columns for id, total amount, and a relationship to the user who placed the order.
Relationships between 2 tables/entities are usually defined via a foreign key column so these are also defined alongside other columns. The definition usually requires 2 lines. A column to contain the foreign key id and the other to specify the name used to access related entity which usually looks like:
Prisma provide a database migration tool to ease schema changes. To apply the changes to the database, you need to run the following command:
This command will create a new migration file in the prisma/migrations directory and apply the changes to the database. You should see changes in the database schema after running this command.
Essential for e-commerce applications (e.g., products, orders, customers).
getOrdersByUserId function takes a userId as a parameter and returns all orders placed by that user.findMany method is used to fetch multiple records from the order table.where clause filters the orders based on the userId.include clause is used to fetch related user data along with the orders.By writing custom queries in Prisma, you can efficiently interact with your database and fetch the data you need for your Remix application. Prisma handles the SQL query generation and creates JavaScript instances of your data, simplifying database operations.
Use the Prisma client to query the database in your Remix loaders or actions. For example, create a file named products.jsx in the app/routes directory:
import { useLoaderData } from 'remix';
import prisma from '../prisma';
export let loader = async () => {
const products = await prisma.product.findMany();
return products;
};
export default function Products() {
let products = useLoaderData();
return (
<div>
<h1>Products</h1>
<ul>
{products.map(product => (
<li key={product.id}>
{product.name} - ${product.price}
</li>
))}
</ul>
</div>
);
}By understanding these key points, you will have a solid foundation for building modern web applications using JavaScript, React, Remix, Prisma, and Tailwind CSS. The practical examples and hands-on practice will help reinforce your learning and enable you to apply these concepts in real-world scenarios.
By understanding these key points, you will have a solid foundation for building modern web applications using JavaScript, React, and Remix. The practical examples and hands-on practice will help reinforce your learning and enable you to apply these concepts in real-world scenarios.
These resources will help you deepen your understanding of JavaScript, TypeScript, React, Remix, PostgreSQL, Prisma, and Tailwind CSS, and provide valuable reference material as you build your web applications.
By following these practice exercises, you will gain hands-on experience with Remix and build a functional e-commerce website. The provided Git repository will serve as a valuable resource for learning and reference.
E. Bruno - Introduction to Web Development with Remix