diff --git a/examples/aws-solid-container/.dockerignore b/examples/aws-solid-container/.dockerignore new file mode 100644 index 000000000..ea0aaeeec --- /dev/null +++ b/examples/aws-solid-container/.dockerignore @@ -0,0 +1,5 @@ +node_modules + + +# sst +.sst \ No newline at end of file diff --git a/examples/aws-solid-container/.gitignore b/examples/aws-solid-container/.gitignore new file mode 100644 index 000000000..8ebae3024 --- /dev/null +++ b/examples/aws-solid-container/.gitignore @@ -0,0 +1,32 @@ + +dist +.solid +.output +.vercel +.netlify +.vinxi +app.config.timestamp_*.js + +# Environment +.env +.env*.local + +# dependencies +/node_modules + +# IDEs and editors +/.idea +.project +.classpath +*.launch +.settings/ + +# Temp +gitignore + +# System Files +.DS_Store +Thumbs.db + +# sst +.sst diff --git a/examples/aws-solid-container/Dockerfile b/examples/aws-solid-container/Dockerfile new file mode 100644 index 000000000..eb842b02d --- /dev/null +++ b/examples/aws-solid-container/Dockerfile @@ -0,0 +1,23 @@ +FROM node:lts AS base + +WORKDIR /src + +# Build +FROM base as build + +COPY --link package.json package-lock.json ./ +RUN npm install + +COPY --link . . + +RUN npm run build + +# Run +FROM base + +ENV PORT=3000 +ENV NODE_ENV=production + +COPY --from=build /src/.output /src/.output + +CMD [ "node", ".output/server/index.mjs" ] diff --git a/examples/aws-solid-container/README.md b/examples/aws-solid-container/README.md new file mode 100644 index 000000000..a84af3943 --- /dev/null +++ b/examples/aws-solid-container/README.md @@ -0,0 +1,32 @@ +# SolidStart + +Everything you need to build a Solid project, powered by [`solid-start`](https://start.solidjs.com); + +## Creating a project + +```bash +# create a new project in the current directory +npm init solid@latest + +# create a new project in my-app +npm init solid@latest my-app +``` + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: + +```bash +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +Solid apps are built with _presets_, which optimise your project for deployment to different environments. + +By default, `npm run build` will generate a Node app that you can run with `npm start`. To use a different preset, add it to the `devDependencies` in `package.json` and specify in your `app.config.js`. + +## This project was created with the [Solid CLI](https://solid-cli.netlify.app) diff --git a/examples/aws-solid-container/app.config.ts b/examples/aws-solid-container/app.config.ts new file mode 100644 index 000000000..de7f83103 --- /dev/null +++ b/examples/aws-solid-container/app.config.ts @@ -0,0 +1,3 @@ +import { defineConfig } from "@solidjs/start/config"; + +export default defineConfig({}); diff --git a/examples/aws-solid-container/package.json b/examples/aws-solid-container/package.json new file mode 100644 index 000000000..9d85a8ee9 --- /dev/null +++ b/examples/aws-solid-container/package.json @@ -0,0 +1,25 @@ +{ + "name": "example-basic", + "type": "module", + "scripts": { + "build": "vinxi build", + "dev": "vinxi dev", + "start": "vinxi start", + "version": "vinxi version" + }, + "dependencies": { + "@solidjs/meta": "^0.29.4", + "@solidjs/router": "^0.14.8", + "@solidjs/start": "^1.0.8", + "ioredis": "^5.4.1", + "solid-js": "^1.9.1", + "sst": "latest", + "vinxi": "^0.4.3" + }, + "engines": { + "node": ">=18" + }, + "overrides": { + "nitropack": "npm:nitropack-nightly@latest" + } +} diff --git a/examples/aws-solid-container/public/favicon.ico b/examples/aws-solid-container/public/favicon.ico new file mode 100644 index 000000000..fb282da07 Binary files /dev/null and b/examples/aws-solid-container/public/favicon.ico differ diff --git a/examples/aws-solid-container/src/app.css b/examples/aws-solid-container/src/app.css new file mode 100644 index 000000000..8596998a4 --- /dev/null +++ b/examples/aws-solid-container/src/app.css @@ -0,0 +1,39 @@ +body { + font-family: Gordita, Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; +} + +a { + margin-right: 1rem; +} + +main { + text-align: center; + padding: 1em; + margin: 0 auto; +} + +h1 { + color: #335d92; + text-transform: uppercase; + font-size: 4rem; + font-weight: 100; + line-height: 1.1; + margin: 4rem auto; + max-width: 14rem; +} + +p { + max-width: 14rem; + margin: 2rem auto; + line-height: 1.35; +} + +@media (min-width: 480px) { + h1 { + max-width: none; + } + + p { + max-width: none; + } +} diff --git a/examples/aws-solid-container/src/app.tsx b/examples/aws-solid-container/src/app.tsx new file mode 100644 index 000000000..d1359c8d8 --- /dev/null +++ b/examples/aws-solid-container/src/app.tsx @@ -0,0 +1,22 @@ +import { MetaProvider, Title } from "@solidjs/meta"; +import { Router } from "@solidjs/router"; +import { FileRoutes } from "@solidjs/start/router"; +import { Suspense } from "solid-js"; +import "./app.css"; + +export default function App() { + return ( + ( + + SolidStart - Basic + Index + About + {props.children} + + )} + > + + + ); +} diff --git a/examples/aws-solid-container/src/components/Counter.css b/examples/aws-solid-container/src/components/Counter.css new file mode 100644 index 000000000..220e17946 --- /dev/null +++ b/examples/aws-solid-container/src/components/Counter.css @@ -0,0 +1,21 @@ +.increment { + font-family: inherit; + font-size: inherit; + padding: 1em 2em; + color: #335d92; + background-color: rgba(68, 107, 158, 0.1); + border-radius: 2em; + border: 2px solid rgba(68, 107, 158, 0); + outline: none; + width: 200px; + font-variant-numeric: tabular-nums; + cursor: pointer; +} + +.increment:focus { + border: 2px solid #335d92; +} + +.increment:active { + background-color: rgba(68, 107, 158, 0.2); +} \ No newline at end of file diff --git a/examples/aws-solid-container/src/components/Counter.tsx b/examples/aws-solid-container/src/components/Counter.tsx new file mode 100644 index 000000000..091fc5d0b --- /dev/null +++ b/examples/aws-solid-container/src/components/Counter.tsx @@ -0,0 +1,11 @@ +import { createSignal } from "solid-js"; +import "./Counter.css"; + +export default function Counter() { + const [count, setCount] = createSignal(0); + return ( + + ); +} diff --git a/examples/aws-solid-container/src/entry-client.tsx b/examples/aws-solid-container/src/entry-client.tsx new file mode 100644 index 000000000..0ca4e3c30 --- /dev/null +++ b/examples/aws-solid-container/src/entry-client.tsx @@ -0,0 +1,4 @@ +// @refresh reload +import { mount, StartClient } from "@solidjs/start/client"; + +mount(() => , document.getElementById("app")!); diff --git a/examples/aws-solid-container/src/entry-server.tsx b/examples/aws-solid-container/src/entry-server.tsx new file mode 100644 index 000000000..401eff83f --- /dev/null +++ b/examples/aws-solid-container/src/entry-server.tsx @@ -0,0 +1,21 @@ +// @refresh reload +import { createHandler, StartServer } from "@solidjs/start/server"; + +export default createHandler(() => ( + ( + + + + + + {assets} + + +
{children}
+ {scripts} + + + )} + /> +)); diff --git a/examples/aws-solid-container/src/global.d.ts b/examples/aws-solid-container/src/global.d.ts new file mode 100644 index 000000000..dc6f10c22 --- /dev/null +++ b/examples/aws-solid-container/src/global.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/aws-solid-container/src/routes/[...404].tsx b/examples/aws-solid-container/src/routes/[...404].tsx new file mode 100644 index 000000000..4ea71ec7f --- /dev/null +++ b/examples/aws-solid-container/src/routes/[...404].tsx @@ -0,0 +1,19 @@ +import { Title } from "@solidjs/meta"; +import { HttpStatusCode } from "@solidjs/start"; + +export default function NotFound() { + return ( +
+ Not Found + +

Page Not Found

+

+ Visit{" "} + + start.solidjs.com + {" "} + to learn how to build SolidStart apps. +

+
+ ); +} diff --git a/examples/aws-solid-container/src/routes/about.tsx b/examples/aws-solid-container/src/routes/about.tsx new file mode 100644 index 000000000..8371d911c --- /dev/null +++ b/examples/aws-solid-container/src/routes/about.tsx @@ -0,0 +1,10 @@ +import { Title } from "@solidjs/meta"; + +export default function Home() { + return ( +
+ About +

About

+
+ ); +} diff --git a/examples/aws-solid-container/src/routes/index.tsx b/examples/aws-solid-container/src/routes/index.tsx new file mode 100644 index 000000000..ff472de40 --- /dev/null +++ b/examples/aws-solid-container/src/routes/index.tsx @@ -0,0 +1,30 @@ +import { Resource } from "sst"; +import { Cluster } from "ioredis"; +import { createAsync, cache } from "@solidjs/router"; + +const getCounter = cache(async () => { + "use server"; + const redis = new Cluster( + [{ host: Resource.MyRedis.host, port: Resource.MyRedis.port }], + { + dnsLookup: (address, callback) => callback(null, address), + redisOptions: { + tls: {}, + username: Resource.MyRedis.username, + password: Resource.MyRedis.password, + }, + } + ); + + return await redis.incr("counter"); +}, "counter"); + +export const route = { + load: () => getCounter(), +}; + +export default function Page() { + const counter = createAsync(() => getCounter()); + + return

Hit counter: {counter()}

; +} diff --git a/examples/aws-solid-container/sst-env.d.ts b/examples/aws-solid-container/sst-env.d.ts new file mode 100644 index 000000000..261f14f5b --- /dev/null +++ b/examples/aws-solid-container/sst-env.d.ts @@ -0,0 +1,25 @@ +/* This file is auto-generated by SST. Do not edit. */ +/* tslint:disable */ +/* eslint-disable */ +import "sst" +export {} +declare module "sst" { + export interface Resource { + "MyRedis": { + "host": string + "password": string + "port": number + "type": "sst.aws.Redis" + "username": string + } + "MyService": { + "service": string + "type": "sst.aws.Service" + "url": string + } + "MyVpc": { + "bastion": string + "type": "sst.aws.Vpc" + } + } +} diff --git a/examples/aws-solid-container/sst.config.ts b/examples/aws-solid-container/sst.config.ts new file mode 100644 index 000000000..ec95baaee --- /dev/null +++ b/examples/aws-solid-container/sst.config.ts @@ -0,0 +1,26 @@ +/// + +export default $config({ + app(input) { + return { + name: "aws-solid-container", + removal: input?.stage === "production" ? "retain" : "remove", + home: "aws", + }; + }, + async run() { + const vpc = new sst.aws.Vpc("MyVpc", { bastion: true }); + const redis = new sst.aws.Redis("MyRedis", { vpc }); + const cluster = new sst.aws.Cluster("MyCluster", { vpc }); + + cluster.addService("MyService", { + link: [redis], + public: { + ports: [{ listen: "80/http", forward: "3000/http" }], + }, + dev: { + command: "npm run dev", + }, + }); + } +}); diff --git a/examples/aws-solid-container/tsconfig.json b/examples/aws-solid-container/tsconfig.json new file mode 100644 index 000000000..7d5871a07 --- /dev/null +++ b/examples/aws-solid-container/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "allowJs": true, + "strict": true, + "noEmit": true, + "types": ["vinxi/types/client"], + "isolatedModules": true, + "paths": { + "~/*": ["./src/*"] + } + } +} diff --git a/www/src/assets/docs/start/start-solidstart-container.png b/www/src/assets/docs/start/start-solidstart-container.png new file mode 100644 index 000000000..dff4d2f58 Binary files /dev/null and b/www/src/assets/docs/start/start-solidstart-container.png differ diff --git a/www/src/content/docs/docs/start/aws/astro.mdx b/www/src/content/docs/docs/start/aws/astro.mdx index a827627a8..aac727d84 100644 --- a/www/src/content/docs/docs/start/aws/astro.mdx +++ b/www/src/content/docs/docs/start/aws/astro.mdx @@ -261,7 +261,7 @@ npm install Select the defaults and pick **AWS**. This'll create a `sst.config.ts` file in your project root. -It'll also ask you to update your `astro.config.mjs` but we'll instead use the [**Node.js adapter**](https://docs.astro.build/en/guides/integrations-guide/node/) since we're deploying it through a container. +It'll also ask you to update your `astro.config.mjs`. But we'll instead use the [**Node.js adapter**](https://docs.astro.build/en/guides/integrations-guide/node/) since we're deploying it through a container. ```bash npx astro add node diff --git a/www/src/content/docs/docs/start/aws/solid.mdx b/www/src/content/docs/docs/start/aws/solid.mdx index f3ea02d1f..990fd8a2a 100644 --- a/www/src/content/docs/docs/start/aws/solid.mdx +++ b/www/src/content/docs/docs/start/aws/solid.mdx @@ -3,7 +3,18 @@ title: SolidStart on AWS with SST description: Create and deploy a SolidStart app to AWS with SST. --- -We are going to create a SolidStart app, add an S3 Bucket for file uploads, and deploy it to AWS using SST. +There are two ways to deploy SolidStart apps to AWS with SST. + +1. [Serverless](#serverless) +2. [Containers](#containers) + +We'll use both to build a couple of simple apps below. + +--- + +## Serverless + +We are going to create a SolidStart app, add an S3 Bucket for file uploads, and deploy it using the `SolidStart` component. :::tip[View source] You can [view the source](https://github.com/sst/ion/tree/dev/examples/aws-solid-start) of this example in our repo. @@ -13,20 +24,20 @@ Before you get started, make sure to [configure your AWS credentials](/docs/iam- --- -## 1. Create a project +### 1. Create a project Let's start by creating our project. ```bash -npm init solid@latest my-solid-app -cd my-solid-app +npm init solid@latest aws-solid-start +cd aws-solid-start ``` We are picking the **_bare_**, and **_TypeScript_** options. --- -#### Init SST +##### Init SST Now let's initialize SST in our app. @@ -49,7 +60,7 @@ export default defineConfig({ --- -#### Start dev mode +##### Start dev mode Run the following to start dev mode. This'll start SST and your SolidStart app. @@ -61,7 +72,7 @@ Once complete, click on **MyWeb** in the sidebar and open your SolidStart app in --- -## 2. Add an S3 Bucket +### 2. Add an S3 Bucket Let's allow public `access` to our S3 Bucket for file uploads. Update your `sst.config.ts`. @@ -73,7 +84,7 @@ const bucket = new sst.aws.Bucket("MyBucket", { Add this above the `SolidStart` component. -#### Link the bucket +##### Link the bucket Now, link the bucket to our SolidStart app. @@ -85,7 +96,7 @@ new sst.aws.SolidStart("MyWeb", { --- -## 3. Generate a pre-signed URL +### 3. Generate a pre-signed URL When our app loads, we'll generate a pre-signed URL for the file upload and use it in our form. Add this below the imports in `src/app.tsx`. @@ -125,7 +136,7 @@ npm install @solidjs/router @aws-sdk/client-s3 @aws-sdk/s3-request-presigner --- -## 4. Create an upload form +### 4. Create an upload form Add a form to upload files to the presigned URL. Replace the `App()` component in `src/app.tsx` with: @@ -166,7 +177,7 @@ Head over to the local app in your browser, `http://localhost:3000` and try **up --- -## 5. Deploy your app +### 5. Deploy your app Now let's deploy your app to AWS. @@ -182,6 +193,249 @@ Congrats! Your site should now be live! --- +## Containers + +We are going to build a hit counter SolidStart app with Redis. We'll deploy it to AWS in a container using the `Cluster` component. + +:::tip[View source] +You can [view the source](https://github.com/sst/ion/tree/dev/examples/aws-solid-container) of this example in our repo. +::: + +Before you get started, make sure to [configure your AWS credentials](/docs/iam-credentials#credentials). + +--- + +### 1. Create a project + +Let's start by creating our project. + +```bash +npm init solid@latest aws-solid-start +cd aws-solid-start +``` + +We are picking the **_basic_**, and **_TypeScript_** options. + +--- + +##### Init SST + +Now let's initialize SST in our app. + +```bash +npx sst@latest init +npm install +``` + +Select the defaults and pick **AWS**. This'll create a `sst.config.ts` file in your project root. + +It'll also ask you to update your `app.config.ts`. Instead we'll use the **default Node preset**. + +```ts title="app.config.ts" +import { defineConfig } from "@solidjs/start/config"; + +export default defineConfig({}); +``` + +--- + +### 2. Add a Cluster + +To deploy our SolidStart app in a container, we'll use [AWS Fargate](https://aws.amazon.com/fargate/) with [Amazon ECS](https://aws.amazon.com/ecs/). Replace the `run` function in your `sst.config.ts`. + +```js title="sst.config.ts" {9-11} +async run() { + const vpc = new sst.aws.Vpc("MyVpc", { bastion: true }); + const cluster = new sst.aws.Cluster("MyCluster", { vpc }); + + cluster.addService("MyService", { + public: { + ports: [{ listen: "80/http", forward: "4321/http" }], + }, + dev: { + command: "npm run dev", + }, + }); +} +``` + +This creates a VPC with a bastion host, an ECS Cluster, and adds a Fargate service to it. + +The `dev.command` tells SST to run our SolidStart app locally in dev mode. + +--- + +### 3. Add Redis + +Let's add an [Amazon ElastiCache](https://aws.amazon.com/elasticache/) Redis cluster. Add this below the `Vpc` component in your `sst.config.ts`. + +```js title="sst.config.ts" +const redis = new sst.aws.Redis("MyRedis", { vpc }); +``` + +This shares the same VPC as our ECS cluster. + +--- + +#### Link Redis + +Now, link the Redis cluster to the container. + +```ts title="sst.config.ts" {3} +cluster.addService("MyService", { + // ... + link: [redis], +}); +``` + +This will allow us to reference the Redis cluster in our SolidStart app. + +--- + +#### Install a tunnel + +Since our Redis cluster is in a VPC, we'll need a tunnel to connect to it from our local machine. + +```bash "sudo" +sudo npx sst tunnel install +``` + +This needs _sudo_ to create a network interface on your machine. You'll only need to do this once on your machine. + +--- + +#### Start dev mode + +Start your app in dev mode. + +```bash +npx sst dev +``` + +This will deploy your app, start a tunnel in the **Tunnel** tab, and run your SolidStart app locally in the **MyServiceDev** tab. + +--- + +### 4. Connect to Redis + +We want the `/` route to increment a counter in our Redis cluster. Let's start by installing the packages we'll use. + +```bash +npm install ioredis @solidjs/router +``` + +We'll increment the counter when the route loads. Replace your `src/routes/index.tsx` with: + +```ts title="src/routes/index.tsx" {8} +import { Resource } from "sst"; +import { Cluster } from "ioredis"; +import { createAsync, cache } from "@solidjs/router"; + +const getCounter = cache(async () => { + "use server"; + const redis = new Cluster( + [{ host: Resource.MyRedis.host, port: Resource.MyRedis.port }], + { + dnsLookup: (address, callback) => callback(null, address), + redisOptions: { + tls: {}, + username: Resource.MyRedis.username, + password: Resource.MyRedis.password, + }, + } + ); + + return await redis.incr("counter"); +}, "counter"); + +export const route = { + load: () => getCounter(), +}; +``` + +:::tip +We are directly accessing our Redis cluster with `Resource.MyRedis.*`. +::: + +Let's update our component to show the counter. Add this to your `src/routes/index.tsx`. + +```tsx title="src/routes/index.tsx" +export default function Page() { + const counter = createAsync(() => getCounter()); + + return

Hit counter: {counter()}

; +} +``` + +--- + +#### Test your app + +Let's head over to `http://localhost:3000` in your browser and it'll show the current hit counter. + +You should see it increment every time you **refresh the page**. + +--- + +### 5. Deploy your app + +To deploy our app we'll add a `Dockerfile`. + +
+View Dockerfile + +```Dockerfile title="Dockerfile" +FROM node:lts AS base + +WORKDIR /src + +# Build +FROM base as build + +COPY --link package.json package-lock.json ./ +RUN npm install + +COPY --link . . + +RUN npm run build + +# Run +FROM base + +ENV PORT=3000 +ENV NODE_ENV=production + +COPY --from=build /src/.output /src/.output + +CMD [ "node", ".output/server/index.mjs" ] +``` + +
+ +:::tip +You need to be running [Docker Desktop](https://www.docker.com/products/docker-desktop/) to deploy your app. +::: + +Let's also add a `.dockerignore` file in the root. + +```bash title=".dockerignore" +node_modules +``` + +Now to build our Docker image and deploy we run: + +```bash +npx sst deploy --stage production +``` + +You can use any stage name here but it's good to create a new stage for production. + +Congrats! Your app should now be live! + +![SST SolidStart container app](../../../../../assets/docs/start/start-solidstart-container.png) + +--- + ## Connect the console As a next step, you can setup the [SST Console](/docs/console/) to _**git push to deploy**_ your app and monitor it for any issues.