Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a basic web application tutorial using smithy #101

Merged
merged 26 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ bin/

# Ignore Gradle build output directory
build

# Ignore node_modules, next cache
node_modules
.next
7 changes: 6 additions & 1 deletion smithy-templates.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@
".gitignore",
".gitattributes"
]
}
},
"smithy-tutorial": {
"documentation": "Tutorial project that demonstrates how to use the Smithy in a simple web application and server.",
haydenbaker marked this conversation as resolved.
Show resolved Hide resolved
"path": "tutorial",
"include": [".gitignore",".gitattributes"]
}
}
}
46 changes: 46 additions & 0 deletions tutorial/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@

clean:
haydenbaker marked this conversation as resolved.
Show resolved Hide resolved
@echo Cleaning build Directories...
rm -rf build/ */build/ */dist */node_modules client/sdk server/ssdk app/.next
@echo Cleaning Complete.

build-smithy:
@echo Building smithy models...
cd smithy; smithy build model/
haydenbaker marked this conversation as resolved.
Show resolved Hide resolved
@echo Finished building models.

build-ssdk: build-smithy
@echo Building server-sdk...
cd server; ln -fs ../smithy/build/smithy/source/typescript-ssdk-codegen ssdk
cd server/ssdk; yarn && yarn build
@echo Finished building server-sdk.

build-server: build-ssdk
@echo Building server...
cd server; yarn && yarn build; yarn install
@echo Finished building server.

build-client: build-smithy
@echo Building client...
cd client; ln -fs ../smithy/build/smithy/source/typescript-client-codegen sdk
haydenbaker marked this conversation as resolved.
Show resolved Hide resolved
cd client/sdk; yarn && yarn build
@echo Finished building client.

build-app: build-client
@echo Building app...
cd app; yarn && yarn build
@echo Finished building app.

build: build-server build-client build-app

dev-server: build-ssdk
cd server; yarn && yarn dev

dev-app: build-client
haydenbaker marked this conversation as resolved.
Show resolved Hide resolved
cd app; yarn && yarn dev

run-server: build-server
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make the port configurable with a default to 8080 or something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to - confiigurability isn't the point

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I generally agree, sometimes users will already have things running on a given port and we should give them a built-in workaround.

Copy link
Contributor Author

@haydenbaker haydenbaker Jul 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's always a workaround - changing the port in the app and/or server. 3000 is the default for nextjs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would still prefer to allow it to be overridden with an arg (i.e. make server 8080) but it is non-blocking.

cd server; yarn start

run-app:
haydenbaker marked this conversation as resolved.
Show resolved Hide resolved
cd app; yarn start
65 changes: 65 additions & 0 deletions tutorial/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Smithy Tutorial
This package serves as an introduction to a simple web application that uses a smithy-based client to make calls to a smithy-based server. This project is laid out in the following way for ease of learning:
haydenbaker marked this conversation as resolved.
Show resolved Hide resolved

### Model - `smithy/`
This directory contains the smithy model and smithy build configuration.
haydenbaker marked this conversation as resolved.
Show resolved Hide resolved

### Server - `server/`
This directory contains the server implementation, which is based on the generated server-sdk built from the smithy model.
haydenbaker marked this conversation as resolved.
Show resolved Hide resolved

### Client - `client/`
This directory is empty until the generated client is built from the smithy model. After a build, this will be populated with a code-generated client.

### Web application - `app/`
This directory contains the Next.js web-application that consumes the generated client and makes call to the server.

## Building
To build each of the packages, there are several distinct targets in the `Makefile`.

To build everything, run:
```
make build
```
This will build each distinct component of this project: the smithy model (`smithy/`), the client (`client/`), the application (`app/`), and the server (`server/`).

You may also independently build each component:
* `make build-smithy`: build the smithy model and code-generate the client and server
* `make build-ssdk`: set up and build the generated server-sdk (`ssdk`)
* `make build-server`: build the server implementation
* `make build-client`: set up and build the generated client
* `make build-app`: build the web application

If you would like to clean up the project, run:
```
make clean
```
This will clean out all of the build artifacts for the entire project. You shouldn't need to do this unless you would like to have a completely clean slate.

## Running
### Server
To run the server in development mode, run:
```
make dev-server
```
The server will hot-reload on changes to the server code, making it easier to test small changes. The hot-reload behavior is useful for testing small changes to the server implementation, but cannot be used to test model changes without rebuilding the server-sdk (`make build-ssdk`) in this mode.

To run the server in production mode, run:
```
make run-server
```

Whether you launch in development mode or not, the server will run on `localhost:3001`. The web-application will make requests to this address.

### Web Application
To run the web-application in development mode, run:
```
make dev-app
```
The application will hot-load changes to the app, making it easier to test your changes. Any changes you make to the smithy model will require you to rebuild the client (`make build-client`) for them to reflect in this mode.

To run the app in production mode, run:
```
make run-app
```

Whether you launch in development mode or not, the web-application will run on `localhost:3000`. To access it, view this [address](http://localhost:3000) in your browser.
3 changes: 3 additions & 0 deletions tutorial/app/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
2 changes: 2 additions & 0 deletions tutorial/app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# App
TODO
haydenbaker marked this conversation as resolved.
Show resolved Hide resolved
92 changes: 92 additions & 0 deletions tutorial/app/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 230, 210, 200;
--background-end-rgb: 255, 255, 255;
}

@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}

body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}

@layer utilities {
.text-balance {
text-wrap: balance;
}
}

/* Home styling */
.home {
@apply flex min-h-screen flex-col items-center justify-between p-24
}

.home_header {
@apply z-10 w-full max-w-7xl items-center justify-between font-sans text-sm lg:flex
}

.home_header_content {
@apply fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:size-auto lg:bg-none
}

/* ----------- */

/* Menu styling */
.home_menu {
@apply grid gap-8 pt-14;
}

.menu-item_card {
@apply inline-flex p-6 justify-center items-start bg-orange-50 hover:bg-orange-100 hover:shadow-md rounded-3xl;
}

.menu-item_content {
@apply w-10 justify-between items-start gap-2 h-full text-nowrap;
}

.menu-item_name {
@apply text-[22px] leading-[26px] font-bold capitalize;
}

.menu-item_image_container {
}

.menu-item_image {
@apply relative w-full h-60 my-3 object-contain right-4;
}

.menu-item_description {
}

.menu-item_box {
@apply h-full
}

.menu-item_box_description {
@apply font-bold
}

.menu-item_box_containers {
@apply h-48 w-80 flex flex-col place-content-evenly list-disc text-balance
}
/* ----------- */

.order_button {
@apply w-full py-[16px] rounded-3xl bg-orange-200 hover:bg-orange-50
}
83 changes: 83 additions & 0 deletions tutorial/app/app/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { CoffeeItem, CoffeeService, CoffeeType, OrderStatus } from "@com.example/coffee-service-client";

// Convert a string to a "friendly" variant
export function displayName(str: string): string {
const tmp = str ? str.replaceAll("_", " ").toLowerCase() : ""
return tmp.replace(/\b(\w)/g, s => s.toUpperCase());
}

// Get the right image for a given coffee type
export function getImage(type: CoffeeType | string): string {
switch (type) {
case CoffeeType.POUR_OVER:
return "/pour-over.png"
case CoffeeType.DRIP:
return "/drip.png"
case CoffeeType.LATTE:
return "/latte.png"
case CoffeeType.ESPRESSO:
return "/espresso.png"
default:
return "/pour-over.png"
}
}

// create a coffee service client singleton and getter
let client: CoffeeService
export function getClient(): CoffeeService {
return client || (client = new CoffeeService({
endpoint: {
protocol: "http",
hostname: "localhost",
port: 3001,
path: "/"
}
}));
}

// coffee service client helpers ------
export async function getMenuItems(): Promise<CoffeeItem[]> {
let items: CoffeeItem[] = []
try {
const res = await getClient().getMenu();
items = res.items || []
} catch (err) {
console.log(err)
}
return items
}

export async function submitOrder(cofeeType: CoffeeType): Promise<string> {
let orderId: string = ""
try {
const res = await getClient().createOrder({
coffeeType: cofeeType
});
orderId = res.id || ""
} catch (err) {
console.log(err)
}
return orderId
}

export interface Order {
coffeeType: CoffeeType;
status: OrderStatus;
}

export async function getOrder(orderId: string): Promise<Order | undefined> {
try {
const res = await getClient().getOrder({
id: orderId
});
if (res.coffeeType && res.status) {
return {
coffeeType: res.coffeeType,
status: res.status
}
}
} catch (err) {
console.log(err)
}
}
// ------------------------------------
22 changes: 22 additions & 0 deletions tutorial/app/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
title: "MyCoffeeShop",
description: "Pour. Sip. Enjoy.",
};

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
);
}
31 changes: 31 additions & 0 deletions tutorial/app/app/order/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"use client"

import Image from "next/image";
import { useSearchParams } from "next/navigation";
import { Suspense } from "react";
import { getImage } from "../index";


export default function Page() {
return (
<Suspense>
<main className="home">
<Coffee></Coffee>
</main>
</Suspense>

);
}

function Coffee() {
const params = useSearchParams();
const coffeeType = params.get("coffeeType") ?? "";
const imageSrc = getImage(coffeeType)

return (
<div>
<h2 className="text-center text-6xl">Enjoy!</h2>
<Image className="" src={imageSrc} width={1024} height={1024} alt="coffee-display" priority/>
</div>
);
}
Loading
Loading