Skip to content

Commit

Permalink
Gavin/add api example (#4696)
Browse files Browse the repository at this point in the history
* adds base project

* remove unnecessary imports

* update readme with launch instructions.

* update models with JS shorthand

* rename directory and update imports
  • Loading branch information
gavination authored Jan 23, 2024
1 parent 52900a0 commit 7d5eebf
Show file tree
Hide file tree
Showing 12 changed files with 2,073 additions and 0 deletions.
130 changes: 130 additions & 0 deletions examples/mongodb-credit-check-api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# vuepress v2.x temp and cache directory
.temp
.cache

# Docusaurus cache and generated files
.docusaurus

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
4 changes: 4 additions & 0 deletions examples/mongodb-credit-check-api/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"tabWidth": 2,
"useTabs": false
}
68 changes: 68 additions & 0 deletions examples/mongodb-credit-check-api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Express Credit Check Workflow

This is a simple workflow engine built with:

- XState v5
- TypeScript
- Express

This is a modified version of the express-workflow project that shows how to implement state hydration in the `actorService.ts` file.
It also uses a more complex machine with guards, actions, and parallel states configured.

**NOTE**: This project is _not_ production-ready and is intended for educational purposes.

## Usage

[MongoDB](https://www.mongodb.com/docs/manual/administration/install-community/) should be configured with a database named `creditCheck`.

We recommend installing the [MongoDB Compass app](https://www.mongodb.com/products/tools/compass) to view the contents of your database while you run this project.

Add the connection string to the DB client in the `actorService.ts` file by updating this line:

```typescript
const uri = "<your mongo uri here>/creditCheck";
```

```bash
pnpm install
pnpm start
```

## Endpoints

### POST `/workflows`

Creates a new workflow instance.

```bash
curl -X POST http://localhost:4242/workflows
```

Example response:
`201 - Created`

```json
{
{"message":"New worflow created successfully","workflowId":"uzkjyy"}
}
```

### POST `/workflows/:id`

`200 - OK`

Sends an event to a workflow instance.

```bash
# Replace :id with the workflow ID; e.g. http://localhost:4242/workflows/7ky252
# the body should be JSON
curl -X POST http://localhost:4242/workflows/:id -d '{"type": "Submit", "SSN": "123456789", "lastName": "Bauman", "firstName": "Gavin"}' -H "Content-Type: application/json"
```

### GET `/workflows/:id`

Gets the current state of a workflow instance.

```bash
curl -X GET http://localhost:4242/workflows/:id
```
103 changes: 103 additions & 0 deletions examples/mongodb-credit-check-api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import bodyParser from "body-parser";
import {
collections,
getDurableActor,
initDbConnection,
} from "./services/actorService";
import express from "express";
import { creditCheckMachine } from "./machine";

const app = express();

app.use(bodyParser.json());

// Endpoint to start a new workflow instance
// - Generates a unique ID for the actor
// - Starts the actor
// - Persists the actor state
// - Returns a 201-Created code with the actor ID in the response
app.post("/workflows", async (_req, res) => {
console.log("starting new workflow...");
try {
// Create a new actor and get its ID
const { workflowId } = await getDurableActor({
machine: creditCheckMachine,
});
res
.status(201)
.json({ message: "New worflow created successfully", workflowId });
} catch (err) {
console.log(err);
res.status(500).send("Error starting workflow. Details: " + err);
}
});

// Endpoint to send events to an existing workflow instance
// - Gets the actor ID from request params
// - Gets the persisted state for that actor
// - Starts the actor with the persisted state
// - Sends the event from the request body to the actor
// - Persists the updated state
// - Returns the updated state in the response
app.post("/workflows/:workflowId", async (req, res) => {
const { workflowId } = req.params;
const event = req.body;

try {
const { actor } = await getDurableActor({
machine: creditCheckMachine,
workflowId,
});
actor.send(event);
} catch (err) {
// note: you can (and should!) create custom errors to handle different scenarios and return different status codes
console.log(err);
res.status(500).send("Error sending event. Details: " + err);
}

res
.status(200)
.send(
"Event received. Issue a GET request to see the current workflow state"
);
});

// Endpoint to get the current state of an existing workflow instance
// - Gets the actor ID from request params
// - Gets the persisted state for that actor
// - Returns the persisted state in the response
app.get("/workflows/:workflowId", async (req, res) => {
const { workflowId } = req.params;
const persistedState = await collections.machineStates?.findOne({
workflowId,
});

if (!persistedState) {
return res.status(404).send("Workflow state not found");
}

res.json(persistedState);
});

app.get("/", (_, res) => {
res.send(`
<html>
<body style="font-family: sans-serif;">
<h1>Express Workflow</h1>
<p>Start a new workflow instance:</p>
<pre>curl -X POST http://localhost:4242/workflows</pre>
<p>Send an event to a workflow instance:</p>
<pre>curl -X POST http://localhost:4242/workflows/:workflowId -d '{"type":"TIMER"}'</pre>
<p>Get the current state of a workflow instance:</p>
<pre>curl -X GET http://localhost:4242/workflows/:workflowId</pre>
</body>
</html>
`);
});

// Connect to the DB and start the server
initDbConnection().then(() => {
app.listen(4242, () => {
console.log("Server listening on port 4242");
});
});
Loading

0 comments on commit 7d5eebf

Please sign in to comment.