Skip to main content

Develop Front End

The farrow-vite-react project template already contains simple sample code, now let's unpack the front-end part.

After running the npm run dev command to start the application, you should see the file src/api/greet.ts, which contains roughly the following code:

/**
* This file was generated by farrow-api
* Don't modify it manually
*/
import { createApiPipelineWithUrl, ApiInvokeOptions } from 'farrow-api-client';
/**
* {@label GreetInput}
*/
export type GreetInput = {
/**
* @remarks The name for greeting
*/
name: string;
};
/**
* {@label GreetOutput}
*/
export type GreetOutput = {
/**
* @remarks The greeting came from server
*/
greet: string;
};
export const url = 'http://localhost:3003/api/greet';
export const apiPipeline = createApiPipelineWithUrl(url);
export const api = {
/**
* @remarks Greeting
*/
greet: (input: GreetInput, options?: ApiInvokeOptions) =>
apiPipeline.invoke(
{ type: 'Single', path: ['greet'], input },
options
) as Promise<GreetOutput>,
};

As stated in the comments section at the beginning, this file is automatically generated by farrow-api and should not normally be modified manually.

farrow-api compiles the server-side input schema and output schema into typescript types, and the api service into http-client interface invocation code.

In other words, the front-end inherits the type definition and method invocation code from the server side.

In the src/app.tsx file, we can import the greet.ts file generated by the server side just like import a normal module.

import React, { useState, useEffect } from 'react';
import logo from './logo.svg';
import './App.css';
import { api as GreetApi } from './api/greet';

By aliasing the api as GreetApi, we can introduce multiple generated client.ts and avoid naming conflicts.

Then, we can call GreetApi.greet(input) like a normal method, which internally requests the server-side interface via the http protocol to get and return the specified type of data.

There is no need for the developer typing as MyType to actively mark it up.

function App() {
const [greet, setGreet] = useState('');
useEffect(() => {
const task = async () => {
const result = await GreetApi.greet({
name: `Farrow + React + Vite`,
});
setGreet(result.greet);
};
task().catch((error) => {
console.log('error', error);
});
}, []);
if (!greet) return null;
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>{greet}</p>
<p>
Edit <code>App.tsx</code> and save to test HMR updates.
</p>
<p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
{' | '}
<a
className="App-link"
href="https://github.com/Lucifier129/farrow"
target="_blank"
rel="noopener noreferrer"
>
Learn Farrow
</a>
{' | '}
<a
className="App-link"
href="https://vitejs.dev/guide/features.html"
target="_blank"
rel="noopener noreferrer"
>
Learn Vite
</a>
</p>
</header>
</div>
);
}
export default App;

Whenever a new interface is added on the server side, farrow detects the change and regenerates the latest type definition and code for the interface call.

During the front-end development, we can see whether the types provided by the server are compatible with the previous version in time, and reduce the number of runtime errors caused by the unsynchronized types on the front-end and back-end.

Next, we try to develop a todo service on the server side that includes add, delete, and check.