Skip to content

Repository files navigation

Why TypePoint?

If you're developing a client & server app both in TypeScript, you can leverage TypePoint to DRYly define your RESTful API endpoints to have strongly typed requests and responses, without duplicating or generating code.

Features

  • Strongly typed requests and responses, both on client and server!
  • Endpoints are defined only once
  • No code generation!
  • Validation of request params and body
  • Promise based request handlers & middleware
  • Support for React Hooks

Usage

Install

First we need to install the following packages:

npm add @typepoint/client @typepoint/server @typepoint/shared @typepoint/express

Endpoint Definitions

We start by defining an endpoint in a shared folder. Our example will define an endpoint to return a Todo with a given id passed as a path param.

import{defineEndpoint,Empty}from'@typepoint/shared';exportinterfaceTodo{id: string;title: string;isCompleted: boolean;}exportinterfaceGetTodoRequestParams{id: string;}exportconstgetTodoEndpoint=defineEndpoint<GetTodoRequestParams,Empty,Todo[]>((path)=>path.literal('/api/todos/').param('id'),);

Our getTodoEndpoint variable defines our endpoint, including:

  • The HTTP method (defaults to 'GET')
  • The path to the endpoint: '/api/todos/{id}'
  • The request params type: GetTodoRequestParams
  • The request body type: Empty (no body, because its a GET method)
  • The response body: Todo

Server

Next, let's create a handler for our endpoint in our server.

import{createHandler}from'@typepoint/server';import{getTodoEndpoint}from'../../shared/endpoints/getTodoHandler';import{getTodoById}from'./todoService';exportconstgetTodoHandler=createHandler(getTodoEndpoint,async({ request, response })=>{// Get todo from our async todo service and put it in our response bodyresponse.body=awaitgetTodoById(request.params.id);});

Next we need to create a router in order to route requests to our handlers.

import{Router}from'@typepoint/server';import{getTodoHandler}from'./todos/getTodoHandler';exportconstrouter=newRouter({// We just have one handler for nowhandlers: [getTodoHandler],});

Finally, we need to connect our router to our web server.

importexpress= require('express');import*asbodyParserfrom'body-parser';import{toMiddleware}from'@typepoint/express';import{router}from'./router';asyncfunctionrun(){constapp=express();constport=3001;app.use(bodyParser.json());consthandlerMiddleware=toMiddleware(router);app.use(handlerMiddleware);app.listen(port,()=>{console.log(`API Server running at http://localhost:${port}`);});}run().catch(console.error);

Now if we run ts-node ./server we'll have a working server that will handle getting a todo.

Client

On the front-end, we need to create a Client in order to make requests.

import{TypePointClient}from'@typepoint/client';exportconstclient=newClient({server: 'http://localhost:3001',});

Now we have our TypePointClient created, we can use it anywhere in our front-end.

import{client}from'./typepoint';import{getTodoEndpoint}from'../shared/endpoints/getTodoEndpoint';asyncfunctionshowTodo(){constresponse=awaitclient.fetch(getTodoEndpoint,{params: {id: '1',},});consttodo=response.body;alert(todo.title);}

The above client side code is completely typed. Trying to fetch from the getTodoEndpoint without passing the a string id as a param will cause a design/compile-time error. The response is also completely typed.

React Hooks

The @typepoint/react library provides react hooks to call your endpoints.

Install

npm add @typepoint/react

TypePoint Provider

Before you can start using TypePoint React hooks, you'll need to wrap your root component with a TypePointProvider. This provides the client that the hooks will use.

import*asReactfrom'react';import*asReactDOMfrom'react-dom';import{TypePointClient}from'@typepoint/client';import{TypePointProvider}from'@typepoint/react';import{App}from'./app';constclient=newTypePointClient({// Path to API server, normally this will be an environment variableserver: 'http://localhost:3001',});exportconstAppWithProviders=React.memo(()=>(<TypePointProviderclient={client}><App/></TypePointProvider>));constroot=window.document.getElementById('root');ReactDOM.render(<AppWithProviders/>,root);

useEndpoint hook

useEndpoint is a hook that immediately fetches a given endpoint with the given params and or body.

importReactfrom'react';import{useEndpoint}from'@typepoint/react';import{getTodosEndpoint}from'../shared/endpoints/getTodosEndpoint';constTodoApp=()=>{const{ response }=useEndpoint(getTodosEndpoint,{});consttodos=response?.body??[];return(<ul>{todos.map((todo)=>(<likey={todo.id}>{todo.title}</li>))}</ul>);};exportdefaultTodoApp;

The endpoint will not be fetched unnecessarily, only when the params change. In the above example no params are required so it will only be fetched once.

useEndpoint also returns a refetch function which can be called imperatively to refetch the endpoint with the last used params and body. This is useful for refreshing data.

const{ response, refetch }=useEndpoint(getTodosEndpoint,{});

useEndpoint also returns the loading state of the request, as well as an error if there is one.

constTodoApp=()=>{const{ response }=useEndpoint(getTodosEndpoint,{});const{ response, loading, error }=useEndpoint(getTodosEndpoint,{});if(loading){return<Spinner/>;}if(error){return<div>Something went wrong!</div>;}consttodos=response?.body??[];return(<ul>{todos.map((todo)=>(<likey={todo.id}>{todo.title}</li>))}</ul>);};

useEndpointLazily hook

useEndpointLazily is just like useEndpoint except that it does not immediately fetch the endpoint, instead it provides a fetch function to let you fetch the given endpoint on demand.

importReactfrom'react';import{useEndpointLazily}from'@typepoint/react';import{addTodoEndpoint}from'../shared/endpoints/addTodoEndpoint';/** * Component which can create a new todo. */constNewTodoInput=()=>{const[title,setTitle]=useState('');const{fetch: addTodo}=useEndpointLazily(addTodoEndpoint);constaddTodoWithTitle=useCallback((title: string)=>{addTodo({body: { title }});},[addTodo]);return(<div><inputtype="text"value=><buttontype="button"onClick={addTodoWithTitle}>Add</button></div>
);
};exportdefaultTodoApp;

Examples

Check out the examples repository for example apps that use TypePoint.

Contributing

Got a problem or suggestion? Submit an issue!

Want to contribute? Fork the repository and submit a pull request! 🌩

About

Documentation website for TypePoint.

Resources

Stars

0 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages