Building internal tools, you either love it or hate it. I've built a lot of tools over the years, usually from scratch, some with my own auto-generated UI libraries, but these days I rather don't waste too much time on it anymore. On the other hand I'm still not hooked on no-code platforms. I feel like writing some quick code on your existing services and business logic is usually faster and you don't have to share any secrets with 3rd parties. Setting up the boilerplate and user interfaces of internal tools tends to be most of the work, so as a developer I rather use a low-code alternative. When I saw interval, I was immediately hooked. It allows you to simply write async functions with your own backend code and then automatically generate a frontend that can be used by non-techies.
I feel like combining this with WunderGraph is a match made in heaven. You can use WunderGraph to query all your backend services, like database and 3rd party APIs and then use Interval to build a UI for it.
In this guide, we'll explore how to build an internal tool using WunderGraph and Interval. We'll build a simple tool that allows you to manage users.
Head over to Interval and create a new Interval app. We'll use the typescript
template for this guide, so we can leverage WunderGraphs typesafety.
The create command includes your personal development key, so be sure to copy it from the Interval dashboard.
npx create-interval-app --template=basic --language=ts --personal_development_key=eelco_dev_...
Interval creates a newinterval
by default, if you choose another directory use that instead in the following steps.
cd interval
Next, we'll install WunderGraph. For this example we don't have any existing backend, so we'll start from scratch.
npx create-wundergraph-app --init
Great, now you can run the Interval app.
yarn dev
You'll see a message that says [Interval] 🔗 Connected! Access your actions at: https://interval.com/dashboard/eelco/develop
. This is the URL where you can access your Interval app.
To make sure TypeScript operations work correctly we also need to make a small change to the tsconfig.json file. Copy tsconfig.json to tsconfig.build.json and make the following changes to tsconfig.json.
{
"compilerOptions": {
"target": "esnext",
"skipLibCheck": true,
"strict": true,
"esModuleInterop": true,
"module": "commonjs"
}
}
Let's make sure we run both Interval and Wundergraph with the dev command. First installnpm-run-all
, this allows us to run multiple commands in parallel.
yarn add -D npm-run-all
Now you can run yarn dev
and both Interval and WunderGraph will be running.
The WunderGraph example app comes with a couple of example operations, let's add them to the Interval app. We'll use the WunderGraph TypeScript client to do this.
Edit .wundergraph/wundergraph.config.ts
and add the following lines to codeGenerators
, this will generate the client into the interval src directory.
{
templates: [templates.typescript.client],
path: '../src/generated',
},
Now let's add some routes to our App. Edit src/index.ts
, you'll see there is just a hello_world
route. Let's replace this with a User
route, that gets a user by id.
import Interval, { io } from '@interval/sdk'
import 'dotenv/config' // loads environment variables from .env
import { createClient } from './generated/client'
const client = createClient()
const interval = new Interval({
apiKey: process.env.INTERVAL_KEY,
routes: {
User: async () => {
const id = await io.input.text('Enter a user id')
const { data } = await client.query({
operationName: 'users/get',
input: {
id,
},
})
return data
},
},
})
interval.listen()
Save the file and head over to the Interval dashboard. You'll see that the User
route is now available. You can click on it to test it out.
Let's say we want to be able to edit the user, we can do so by adding a link to a new editUser route.
const interval = new Interval({
apiKey: process.env.INTERVAL_KEY,
routes: {
User: async () => {
const id = await io.input.text('Enter a user id')
const { data } = await client.query({
operationName: 'users/get',
input: {
id,
},
})
if (!data) {
await io.display.heading('User not found')
return
}
await io.display.heading(data?.name, {
description: data.bio,
menuItems: [
{
label: 'Edit user',
action: 'EditUser',
params: { userId: id },
},
],
})
},
EditUser: async () => {
const id = ctx.params.userId as string
const { data: user } = await client.query({
operationName: 'users/get',
input: {
id,
},
})
if (!user) {
io.display.heading('User not found')
return
}
const [name, bio] = await io.group([
io.input.text('Name', { defaultValue: user.name }),
io.input.text('Bio', { defaultValue: user.bio }),
])
await client.mutate({
operationName: 'users/update',
input: {
id,
name,
bio,
},
})
},
},
})
To test this click on the User action and enter a user id. You'll see the user details and a link to edit the user. Click on the link and you'll be able to edit the user.
When you click EditUser from the home screen, the action is completed, which is not very useful. We can fix this by adding a search.
Create a new operation in .wundergraph/operations/users/search.ts
and add the following code.
import { createOperation, z } from '../../generated/wundergraph.factory'
export default createOperation.query({
input: z.object({
query: z.string(),
}),
handler: async ({ input }) => {
return [
{
id: '1',
name: 'Jens',
bio: 'Founder of WunderGraph',
},
{
id: '2',
name: 'Eelco',
bio: 'Senior Developer at WunderGraph',
},
].filter((user) =>
input.query
? user.name.toLowerCase().includes(input.query.toLowerCase())
: true
)
},
})
Now we can add a search to the EditUser route.
EditUser: async () => {
const id = ctx.params.userId as string
const { data } = await client.query({
operationName: 'users/get',
input: {
id,
},
})
let user = data
if (!user) {
user = await io.search('Search for a user', {
renderResult: (user) => ({
label: user?.name || 'Not found',
description: user?.bio,
}),
onSearch: async (query) => {
const { data } = await client.query({
operationName: 'users/search',
input: {
query,
},
})
return data || []
},
})
}
if (!user) {
return io.display.heading('User not found')
}
const [name, bio] = await io.group([
io.input.text('Name', { defaultValue: user.name }),
io.input.text('Bio', { defaultValue: user.bio }),
])
await client.mutate({
operationName: 'users/update',
input: {
id,
name,
bio,
},
})
}
Awesome! We can now search and edit users.
This was just a simple introduction to how you can use WunderGraph with Interval, but the possibilities are endless. By adding extra services to the WunderGraph virtual graph you can easily add more functionality to your Interval app. Like inviting users, refunding payments, creating flows to enrich data, etc.
If you liked this post let me know in the comments or message me on Twitter , I'd love to go deeper into this topic in another post.
Also Published Here