Skip to content

Express frameworks docs + CLI #512

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

Merged
merged 10 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .changeset/strong-lies-burn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@trigger.dev/cli": patch
---

Added Express support to the CLI
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function FrameworkSelector() {
<FrameworkLink to={projectSetupNextjsPath(organization, project)} supported>
<NextjsLogo className="w-32" />
</FrameworkLink>
<FrameworkLink to={projectSetupExpressPath(organization, project)}>
<FrameworkLink to={projectSetupExpressPath(organization, project)} supported>
<ExpressLogo className="w-36" />
</FrameworkLink>
<FrameworkLink to={projectSetupRemixPath(organization, project)} supported>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,116 @@
import { ExpressLogo } from "~/assets/logos/ExpressLogo";
import { FrameworkComingSoon } from "~/components/frameworks/FrameworkComingSoon";
import { ChatBubbleLeftRightIcon, Squares2X2Icon } from "@heroicons/react/20/solid";
import invariant from "tiny-invariant";
import { Feedback } from "~/components/Feedback";
import { PageGradient } from "~/components/PageGradient";
import { InitCommand, RunDevCommand, TriggerDevStep } from "~/components/SetupCommands";
import { StepContentContainer } from "~/components/StepContentContainer";
import { InlineCode } from "~/components/code/InlineCode";
import { BreadcrumbLink } from "~/components/navigation/NavBar";
import { Badge } from "~/components/primitives/Badge";
import { Button, LinkButton } from "~/components/primitives/Buttons";
import { Callout } from "~/components/primitives/Callout";
import { ClipboardField } from "~/components/primitives/ClipboardField";
import { Header1 } from "~/components/primitives/Headers";
import { Paragraph } from "~/components/primitives/Paragraph";
import { StepNumber } from "~/components/primitives/StepNumber";
import { useAppOrigin } from "~/hooks/useAppOrigin";
import { useDevEnvironment } from "~/hooks/useEnvironments";
import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
import { useProjectSetupComplete } from "~/hooks/useProjectSetupComplete";
import { Handle } from "~/utils/handle";
import { trimTrailingSlash } from "~/utils/pathBuilder";
import { projectSetupPath, trimTrailingSlash } from "~/utils/pathBuilder";

export const handle: Handle = {
breadcrumb: (match) => <BreadcrumbLink to={trimTrailingSlash(match.pathname)} title="Express" />,
};

export default function Page() {
const organization = useOrganization();
const project = useProject();
useProjectSetupComplete();
const devEnvironment = useDevEnvironment();
invariant(devEnvironment, "Dev environment must be defined");
const appOrigin = useAppOrigin();

return (
<FrameworkComingSoon
frameworkName="Express"
githubIssueUrl="https://github.com/triggerdotdev/trigger.dev/issues/451"
githubIssueNumber={451}
>
<ExpressLogo className="w-56" />
</FrameworkComingSoon>
<PageGradient>
<div className="mx-auto max-w-3xl">
<div className="flex items-center justify-between">
<Header1 spacing className="text-bright">
Get setup in 5 minutes
</Header1>
<div className="flex items-center gap-2">
<LinkButton
to={projectSetupPath(organization, project)}
variant="tertiary/small"
LeadingIcon={Squares2X2Icon}
>
Choose a different framework
</LinkButton>
<Feedback
button={
<Button variant="tertiary/small" LeadingIcon={ChatBubbleLeftRightIcon}>
I'm stuck!
</Button>
}
defaultValue="help"
/>
</div>
</div>
<div>
<Callout
variant={"info"}
to="https://github.com/triggerdotdev/trigger.dev/discussions/430"
className="mb-8"
>
Trigger.dev has full support for serverless. We will be adding support for long-running
servers soon.
</Callout>
<div>
<StepNumber
stepNumber="1"
title="Manually set up Trigger.dev in your existing Express project"
/>
<StepContentContainer className="flex flex-col gap-2">
<Paragraph className="mt-2">Copy your server API Key to your clipboard:</Paragraph>
<div className="mb-2 flex w-full items-center justify-between">
<ClipboardField
secure
className="w-fit"
value={devEnvironment.apiKey}
variant={"secondary/medium"}
icon={<Badge variant="outline">Server</Badge>}
/>
</div>
<Paragraph>Now follow this guide:</Paragraph>
<LinkButton
to="https://trigger.dev/docs/documentation/guides/manual/express"
variant="primary/medium"
TrailingIcon="external-link"
>
Manual installation guide
</LinkButton>
</StepContentContainer>
<StepNumber stepNumber="2" title="Run your Express app" />
<StepContentContainer>
<RunDevCommand />
<Callout variant="info">
You may be using the `start` script instead, in which case substitute `dev` in the
above commands.
</Callout>
</StepContentContainer>
<StepNumber stepNumber="3" title="Run the CLI 'dev' command" />
<StepContentContainer>
<TriggerDevStep />
</StepContentContainer>
<StepNumber stepNumber="6" title="Wait for Jobs" displaySpinner />
<StepContentContainer>
<Paragraph>This page will automatically refresh.</Paragraph>
</StepContentContainer>
</div>
</div>
</div>
</PageGradient>
);
}
194 changes: 193 additions & 1 deletion docs/_snippets/manual-setup-express.mdx
Original file line number Diff line number Diff line change
@@ -1 +1,193 @@
We're in the process of building support for the Express framework. You can follow along with progress or contribute via [this GitHub issue](https://github.com/triggerdotdev/trigger.dev/issues).
## Installing Required Packages

Start by installing the necessary packages in your Express.js project directory. You can use npm, pnpm, or yarn as your package manager.

<CodeGroup>

```bash npm
npm install @trigger.dev/sdk @trigger.dev/express
```

```bash pnpm
pnpm install @trigger.dev/sdk @trigger.dev/express
```

```bash yarn
yarn add @trigger.dev/sdk @trigger.dev/express
```

</CodeGroup>

<br />

<Note>Ensure that you execute this command within a Express project.</Note>

## Obtaining the Development Server API Key

To locate your development Server API key, login to the [Trigger.dev
dashboard](https://cloud.trigger.dev) and select the Project you want to
connect to. Then click on the Environments & API Keys tab in the left menu.
You can copy your development Server API Key from the field at the top of this page.
(Your development key will start with `tr_dev_`).

## Adding Environment Variables

Create a `.env` file at the root of your project and include your Trigger API key and URL like this:

```bash
TRIGGER_API_KEY=ENTER_YOUR_DEVELOPMENT_API_KEY_HERE
TRIGGER_API_URL=https://api.trigger.dev # this is only necessary if you are self-hosting
```

Replace `ENTER_YOUR_DEVELOPMENT_API_KEY_HERE` with the actual API key obtained from the previous step.

## Configuring the Trigger Client

Create a file for your Trigger client, in this case we create it at `<root>/trigger.(ts/js)`

```ts trigger.(ts/js)
import { TriggerClient } from "@trigger.dev/sdk";

export const client = new TriggerClient({
id: "my-app",
apiKey: process.env.TRIGGER_API_KEY!,
apiUrl: process.env.TRIGGER_API_URL!,
});
```

Replace **"my-app"** with an appropriate identifier for your project.

## Adding the API endpoint

There are a few different options depending on how your Express project is configured.

- App middleware
- Entire app for Trigger.dev (only relevant if it's the only thing your project is for)

Select the appropriate code example from below:

<CodeGroup>

```typescript app middleware
//import the client from the other file
import { client } from "./trigger";
import { createMiddleware } from "@trigger.dev/express";

//import your job files
import "./jobs/example";

//..your existing Express code
const app: Express = express();

//add the middleware
app.use(createMiddleware(client));

//..the rest of your Express code
```

```typescript entire app
//if the entire app is just for Trigger.dev
import { client } from "./trigger";
import { createExpressServer } from "@trigger.dev/express";

//import your job files
import "./jobs/example";

//this creates an app
createExpressServer(client);
```

</CodeGroup>

## Creating the Example Job

Create a Job file. In this case created `<root>/jobs/example.(ts/js)`

```typescript jobs/example.(ts/js)
import { eventTrigger } from "@trigger.dev/sdk";
import { client } from "../trigger";

// your first job
client.defineJob({
id: "example-job",
name: "Example Job",
version: "0.0.1",
trigger: eventTrigger({
name: "example.event",
}),
run: async (payload, io, ctx) => {
await io.logger.info("Hello world!", { payload });

return {
message: "Hello world!",
};
},
});
```

## Adding Configuration to `package.json`

Inside the `package.json` file, add the following configuration under the root object:

```json
"trigger.dev": {
"endpointId": "my-app"
}

```

Replace **"my-app"** with the appropriate identifier you used in the trigger.js configuration file.

## Running

### Run your Express app

Run your Express app locally, like you normally would. For example:

<CodeGroup>

```bash npm
npm run dev
```

```bash pnpm
pnpm run dev
```

```bash yarn
yarn run dev
```

</CodeGroup>

<Note>You might use `npm run start` instead of dev</Note>

### Run the CLI 'dev' command

In a **_separate terminal window or tab_** run:

<CodeGroup>

```bash npm
npx @trigger.dev/cli@latest dev
```

```bash pnpm
pnpm dlx @trigger.dev/cli@latest dev
```

```bash yarn
yarn dlx @trigger.dev/cli@latest dev
```

</CodeGroup>
<br />
<Note>
You can optionally pass the port if you're not running on 3000 by adding
`--port 3001` to the end
</Note>

<Note>
You can optionally pass the hostname if you're not running on localhost by adding
`--hostname <host>`. Example, in case your Express is running on 0.0.0.0: `--hostname 0.0.0.0`.
</Note>
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
},
"dependencies": {
"@types/degit": "^2.8.3",
"boxen": "^7.1.1",
"chalk": "^5.2.0",
"chokidar": "^3.5.3",
"commander": "^9.4.1",
Expand Down
12 changes: 7 additions & 5 deletions packages/cli/src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,18 +129,20 @@ export const initCommand = async (options: InitCommandOptions) => {

await addConfigurationToPackageJson(resolvedPath, resolvedOptions);

await printNextSteps(resolvedOptions, authorizedKey, packageManager, framework);
const projectUrl = `${resolvedOptions.triggerUrl}/orgs/${authorizedKey.organization.slug}/projects/${authorizedKey.project.slug}`;
if (framework.printInstallationComplete) {
await framework.printInstallationComplete(projectUrl);
} else {
await printNextSteps(projectUrl, packageManager, framework);
}
telemetryClient.init.completed(resolvedOptions);
};

async function printNextSteps(
options: ResolvedOptions,
authorizedKey: WhoamiResponse,
projectUrl: string,
packageManager: PackageManager,
framework: Framework
) {
const projectUrl = `${options.triggerUrl}/orgs/${authorizedKey.organization.slug}/projects/${authorizedKey.project.slug}`;

logger.success(`✔ Successfully initialized Trigger.dev!`);

logger.info("Next steps:");
Expand Down
28 changes: 28 additions & 0 deletions packages/cli/src/frameworks/express/express.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import mock from "mock-fs";
import { Express } from ".";
import { getFramework } from "..";
import { pathExists } from "../../utils/fileSystem";

afterEach(() => {
mock.restore();
});

describe("Express project detection", () => {
test("has dependency", async () => {
mock({
"package.json": JSON.stringify({ dependencies: { express: "1.0.0" } }),
});

const framework = await getFramework("", "npm");
expect(framework?.id).toEqual("express");
});

test("no dependency", async () => {
mock({
"package.json": JSON.stringify({ dependencies: { foo: "1.0.0" } }),
});

const framework = await getFramework("", "npm");
expect(framework?.id).not.toEqual("express");
});
});
Loading