Skip to content

Commit 09d60b7

Browse files
Add React and Next starters (#163)
1 parent 6d51e96 commit 09d60b7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+9328
-0
lines changed

ts/nextjs-starter/.gitignore

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
.yarn/install-state.gz
8+
9+
# testing
10+
/coverage
11+
12+
# next.js
13+
/.next/
14+
/out/
15+
16+
# production
17+
/build
18+
19+
# misc
20+
.DS_Store
21+
*.pem
22+
23+
# debug
24+
npm-debug.log*
25+
yarn-debug.log*
26+
yarn-error.log*
27+
28+
# local env files
29+
.env*.local
30+
31+
# vercel
32+
.vercel
33+
34+
# typescript
35+
*.tsbuildinfo
36+
next-env.d.ts
37+
encore.gen.go
38+
encore.gen.cue
39+
/.encore
40+
/encore.gen

ts/nextjs-starter/README.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Next.js + Encore TS Web App Starter
2+
3+
This is an [Encore](https://encore.dev/) + [Next.js](https://nextjs.org/) project starter. It's a great way to learn how to combine Encore's backend
4+
capabilities with a modern web framework — perfect for building a web app.
5+
6+
## Developing locally
7+
8+
### Prerequisite: Installing Encore
9+
10+
If this is the first time you're using Encore, you first need to install the CLI that runs the local development
11+
environment. Use the appropriate command for your system:
12+
13+
- **macOS:** `brew install encoredev/tap/encore`
14+
- **Linux:** `curl -L https://encore.dev/install.sh | bash`
15+
- **Windows:** `iwr https://encore.dev/install.ps1 | iex`
16+
17+
When you have installed Encore, run:
18+
19+
```bash
20+
encore app create --example=ts/nextjs-starter
21+
```
22+
23+
## Running locally
24+
25+
Start the Encore backend:
26+
```bash
27+
encore run
28+
```
29+
30+
In another terminal window, start the Next.js frontend:
31+
```bash
32+
npm run dev
33+
```
34+
35+
Go to [http://localhost:3000](http://localhost:3000) in the browser to see the example frontend.
36+
37+
You can also access Encore's [local developer dashboard](https://encore.dev/docs/observability/dev-dash) on <http://localhost:9400/> to view traces, API documentation, and more.
38+
39+
### Generating a request client
40+
41+
Keep the contract between the backend and frontend in sync by regenerating the request client whenever you make a change
42+
to an Encore endpoint.
43+
44+
```bash
45+
npm run gen # Will create a new request client app/lib/client.ts
46+
```
47+
48+
## Deployment
49+
50+
For this starter, the backend will be deployed to Encore Cloud (or self-hosted) and the frontend to Vercel. The best way to go about doing that is to create a repo on GitHub and push the project there and then deploy the same codebase to both Encore Cloud and to Vercel.
51+
52+
### Encore
53+
54+
You can [self-host](https://encore.dev/docs/self-host/docker-build) the Encore backend or deploy it to Encore Cloud. Follow these steps to deploy your backend to a staging environment in Encore's free development cloud.
55+
56+
1. Create a GitHub repo, commit and push the app.
57+
2. Open your app in the Encore [Cloud Dashboard](https://app.encore.dev).
58+
3. Go to your app settings and link your app to GitHub and select the repo you just created.
59+
4. Commit and push a change (can be anything) to GitHub to trigger a deploy.
60+
61+
You can follow the deploy in the Cloud Dashboard. When the deploy is complete, your app will be available in the cloud.
62+
63+
Then head over to the [Cloud Dashboard](https://app.encore.dev) to monitor your deployment and find your production URL.
64+
65+
From there you can also connect your own AWS or GCP account to use for deployment.
66+
67+
### Next.js on Vercel
68+
69+
The only thing you need to do is to create a new project on Vercel and point it to your newly created GitHup repo.
70+
71+
## CORS configuration
72+
73+
If you are running into CORS issues when calling your Encore API from your frontend then you may need to specify which
74+
origins are allowed to access your API (via browsers). You do this by specifying the `global_cors` key in the `encore.app`
75+
file, which has the following structure:
76+
77+
```js
78+
global_cors: {
79+
// allow_origins_without_credentials specifies the allowed origins for requests
80+
// that don't include credentials. If nil it defaults to allowing all domains
81+
// (equivalent to ["*"]).
82+
"allow_origins_without_credentials": [
83+
"<ORIGIN-GOES-HERE>"
84+
],
85+
86+
// allow_origins_with_credentials specifies the allowed origins for requests
87+
// that include credentials. If a request is made from an Origin in this list
88+
// Encore responds with Access-Control-Allow-Origin: <Origin>.
89+
//
90+
// The URLs in this list may include wildcards (e.g. "https://*.example.com"
91+
// or "https://*-myapp.example.com").
92+
"allow_origins_with_credentials": [
93+
"<DOMAIN-GOES-HERE>"
94+
]
95+
}
96+
```
97+
98+
More information on CORS configuration can be found here: https://encore.dev/docs/develop/cors
99+
100+
## Learn More
101+
102+
- [Encore Documentation](https://encore.dev/docs)
103+
- [Next.js Documentation](https://nextjs.org/docs)

ts/nextjs-starter/api/admin.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { api } from "encore.dev/api";
2+
import log from "encore.dev/log";
3+
import { getAuthData } from "~encore/auth";
4+
5+
// Welcome to Encore!
6+
//
7+
// To run it this starter, execute "encore run" in your favorite shell.
8+
9+
// ==================================================================
10+
11+
// Endpoint that responds with a hardcoded value.
12+
// To call it, run in your terminal:
13+
//
14+
// curl --header "Authorization: dummy-token" http://localhost:4000/admin
15+
//
16+
export const getDashboardData = api(
17+
{
18+
expose: true, // Is publicly accessible
19+
auth: true, // Auth handler validation is required
20+
method: "GET",
21+
path: "/admin",
22+
},
23+
async (): Promise<DashboardData> => {
24+
const userID = getAuthData()!.userID;
25+
log.info("Data requested by user", { userID });
26+
27+
return { value: "Admin stuff" };
28+
},
29+
);
30+
31+
interface DashboardData {
32+
value: string;
33+
}
34+
35+
// ==================================================================
36+
37+
// Encore comes with a built-in development dashboard for
38+
// exploring your API, viewing documentation, debugging with
39+
// distributed tracing, and more. Visit your API URL in the browser:
40+
//
41+
// http://localhost:9400
42+
//
43+
44+
// ==================================================================
45+
46+
// Next steps
47+
//
48+
// 1. Deploy your application to the cloud
49+
//
50+
// git add -A .
51+
// git commit -m 'Commit message'
52+
// git push encore
53+
//
54+
// 2. To continue exploring Encore with TypeScript, check out one of these topics:
55+
//
56+
// Building a REST API: https://encore.dev/docs/tutorials/rest-api
57+
// Services and APIs: https://encore.dev/docs/ts/primitives/services-and-apis

ts/nextjs-starter/api/auth.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { APIError, Gateway, Header, api } from "encore.dev/api";
2+
import { authHandler } from "encore.dev/auth";
3+
4+
interface LoginParams {
5+
email: string;
6+
password: string;
7+
}
8+
9+
export const login = api(
10+
{ expose: true, auth: false, method: "GET", path: "/login" },
11+
async (params: LoginParams): Promise<{ token: string }> => {
12+
// ... get the userID from database or third party service like Auth0 or Clerk ...
13+
// ... create and sign a token ...
14+
15+
return { token: "dummy-token" };
16+
}
17+
);
18+
19+
interface AuthParams {
20+
authorization: Header<"Authorization">;
21+
}
22+
23+
// The function passed to authHandler will be called for all incoming API call that requires authentication.
24+
// Remove if your app does not require authentication.
25+
export const myAuthHandler = authHandler(
26+
async (params: AuthParams): Promise<{ userID: string }> => {
27+
// ... verify and decode token to get the userID ...
28+
// ... get user info from database or third party service like Auth0 or Clerk ...
29+
30+
if (!params.authorization) {
31+
throw APIError.unauthenticated("no token provided");
32+
}
33+
if (params.authorization !== "dummy-token") {
34+
throw APIError.unauthenticated("invalid token");
35+
}
36+
37+
return { userID: "dummy-user-id" };
38+
}
39+
);
40+
41+
export const gateway = new Gateway({ authHandler: myAuthHandler });
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { Service } from "encore.dev/service";
2+
3+
// Encore will consider this directory and all its subdirectories as part of the "admin" service.
4+
// https://encore.dev/docs/ts/primitives/services
5+
export default new Service("api");

ts/nextjs-starter/app/admin/error.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"use client"; // Error components must be Client Components
2+
3+
import { useEffect } from "react";
4+
5+
export default function Error({
6+
error,
7+
}: {
8+
error: Error & { digest?: string };
9+
reset: () => void;
10+
}) {
11+
useEffect(() => {
12+
// Log the error to an error reporting service
13+
console.error(error);
14+
}, [error]);
15+
16+
if (error.message === "invalid auth param") {
17+
return <p>You need to login to view this data</p>;
18+
}
19+
20+
return <p>Something went wrong!</p>;
21+
}

ts/nextjs-starter/app/admin/page.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { redirect } from "next/navigation";
2+
import getRequestClient from "../lib/getRequestClient";
3+
import { api, APIError, ErrCode } from "../lib/client";
4+
5+
export default async function Admin() {
6+
const client = await getRequestClient();
7+
let response: api.DashboardData | undefined;
8+
let error: APIError | undefined;
9+
10+
try {
11+
response = await client.api.getDashboardData();
12+
} catch (err) {
13+
error = err as APIError;
14+
}
15+
16+
if (error) {
17+
if (error.code === ErrCode.Unauthenticated)
18+
redirect("/auth/unauthenticated?from=%2Fadmin");
19+
else throw error;
20+
}
21+
22+
return (
23+
<section>
24+
<h1 className="text-3xl">Admin Dashboard</h1>
25+
<br />
26+
<p>{response?.value}</p>
27+
</section>
28+
);
29+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { redirect } from "next/navigation";
2+
import { cookies } from "next/headers";
3+
import getRequestClient from "../../lib/getRequestClient";
4+
5+
export async function POST(req: Request) {
6+
const data = await req.formData();
7+
const email = (data.get("email") as string) || "incognito";
8+
const password = (data.get("password") as string) || "password";
9+
10+
try {
11+
const client = await getRequestClient();
12+
const response = await client.api.login({ email, password });
13+
(await cookies()).set("auth-token", response.token);
14+
} catch (error) {
15+
console.error("Error logging in", error);
16+
}
17+
18+
return redirect("/");
19+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { redirect } from "next/navigation";
2+
import { cookies } from "next/headers";
3+
4+
export async function POST() {
5+
(await cookies()).delete("auth-token");
6+
return redirect("/");
7+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"use client";
2+
3+
import { useSearchParams } from "next/navigation";
4+
5+
export default function Unauthenticated() {
6+
const searchParams = useSearchParams();
7+
const fromPage = searchParams.get("from");
8+
9+
return (
10+
<section>
11+
<h1 className="text-3xl">Unauthenticated</h1>
12+
<br />
13+
<p>
14+
You need to be logged in to view <code>{fromPage}</code>
15+
</p>
16+
</section>
17+
);
18+
}

0 commit comments

Comments
 (0)