diff --git a/index.html b/index.html index eb3c9e4..d43e324 100644 --- a/index.html +++ b/index.html @@ -221,11 +221,25 @@
The Framework for Building strictly-typed Python and React TypeScript Websites
+Welcome to Beckett, a strictly-typed Flask and React TypeScript application template.
-Beckett combines a Flask server, with a robust React TypeScript UI, to provide a comprehensive full stack framework.
-Beckett features a sophisticated types manager that automatically synchronizes Python Type hints and TypeScript interfaces. This means that as you make changes to your code, Beckett diligently keeps the API interface in perfect harmony.
-While Beckett is strongly opinionated, it significantly enhances productivity by providing a cohesive development experience and reducing the time spent on manual synchronization between frameworks.
+Welcome to Beckett, a strongly-linked Flask and React application template.
+Beckett combines a Flask server, with a React TypeScript UI into a comprehensive full-stack framework for building modern web applications.
+Beckett features a sophisticated types manager that automatically synchronizes Python Type hints and TypeScript interfaces. This means that as you make changes to your API code in the server, Beckett diligently keeps the API Client up to date.
We use cutting edge Python tools including:
+ +And our TypeScript is modern too:
+Both TypeScript and Python hold their positions as two of the most widely used programming languages globally. As a result, they are frequently combined in various projects.
-However, setting up a smooth and efficient development environment that integrates these languages can be a cumbersome and time-consuming process, often leading to a subpar developer experience.
-Thankfully, Beckett steps in to solve this challenge by through it's sophisticated types manager.
+However, setting up a smooth and efficient development environment that integrates these languages can be a cumbersome and time-consuming process, often leading to a subpar developer experience. Thankfully, Beckett steps in to solve this challenge by through it's sophisticated types manager.
+Beckett provides:
+Wanna learn more? Start with the install step to get started.
diff --git a/search/search_index.json b/search/search_index.json index a341a1a..21d7b95 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"\ud83d\udcab Beckett Framework","text":"The Framework for Building strictly-typed Python and React TypeScript Websites
Welcome to Beckett, a strictly-typed Flask and React TypeScript application template.
Beckett combines a Flask server, with a robust React TypeScript UI, to provide a comprehensive full stack framework.
Beckett features a sophisticated types manager that automatically synchronizes Python Type hints and TypeScript interfaces. This means that as you make changes to your code, Beckett diligently keeps the API interface in perfect harmony.
While Beckett is strongly opinionated, it significantly enhances productivity by providing a cohesive development experience and reducing the time spent on manual synchronization between frameworks.
"},{"location":"#built-on-popular-tools","title":"Built on popular tools","text":"Both TypeScript and Python hold their positions as two of the most widely used programming languages globally. As a result, they are frequently combined in various projects.
However, setting up a smooth and efficient development environment that integrates these languages can be a cumbersome and time-consuming process, often leading to a subpar developer experience.
Thankfully, Beckett steps in to solve this challenge by through it's sophisticated types manager.
Wanna learn more? Start with the install step to get started.
"},{"location":"install/","title":"\ud83c\udfd7\ufe0f Install","text":""},{"location":"install/#early-days","title":"Early days","text":"Before this becomes a fully fledged package, this project exists as a template you can fork and build from.
Once forked, all you have to do is run this command:
make build\n
Then finally, you can either run both the Flask and Typescript server in one terminal with:
make dev\n
Or run them independently with:
make serve\nmake web\n
This will start up the development server.
Now you can start the tutorial to build your first Beckett page.
"},{"location":"todo/","title":"\ud83d\udccb TODO","text":"There is a bunch of stuff I want to add to Beckett, feel free to contribute!
meta
class to the PageProps
so things like the html <title>
tag can be updated, etc.make
commands to a beckett
command line tool.Before this becomes a fully fledged package, this project exists as a template you can fork and build from. My advice for getting started is to build ontop of what already exists, and use the existing routes until you understand it, then you can delete it!
Note
This example will work you through making a simple Bookshop app and API.
"},{"location":"tutorial/#add-your-first-view","title":"Add your first view","text":"In src.views
add a new file called books.py
and add the following code:
from src.app import app\nfrom src.beckett.blueprint import BeckettBlueprint\nbeckett = BeckettBlueprint(\"books\", __name__, url_prefix=\"/books\")\n@beckett.route(\"/\")\ndef home():\nreturn dict(title=\"My books\")\napp.register_blueprint(beckett)\n
Then in src.views.__init__
we just need to do a small update as seen on line 2:
from src.views import people, index # noqa\nfrom .books import *\n
This small import will let the Flask/BeckettApp know to include your views file in the application.
At this point you can now visit http://localhost:9000/books/ to see a simple API response.
"},{"location":"tutorial/#turn-it-into-a-react-page","title":"Turn it into a React page","text":"Let's make this simple API response become something far more interesting - a whole react page!
We only need to make a few changes:
src/views/books.pyfrom src.app import app\nfrom src.beckett.blueprint import BeckettBlueprint\nfrom src.beckett.types import PageProps\nbeckett = BeckettBlueprint(\"books\", __name__, url_prefix=\"/books\")\nclass HomePageProps(PageProps):\ntitle: str\n@beckett.route(\"/\")\n@beckett.page()\ndef home() -> HomePageProps:\nreturn HomePageProps(title=\"My books\")\napp.register_blueprint(beckett)\n
Quite a few interesting things should happen when you save the file - you'll see two brand new files appear in the src/js/templates
directory. I know you want to inspect them and see what has changed, but for now let us go over the changes in the books.py
file first:
from src.beckett.types import PageProps\n
The PageProps
baseclass should be used for all React pages.
class HomePageProps(PageProps):\ntitle: str\n
This is the response type of your React page, and will be transformed into an equivalent Typescript interface. You can inspect the sibling interface now in src/js/template/books/home.type.ts
:
export default interface PageProps {\n\"title\": string\n}\n
Notice how the name of the interface has changed? This is because each React page in Beckett only has one set of initial props for a page load, and this naming convention helps to pick it out.
@beckett.page()\ndef home() -> HomePageProps:\nreturn HomePageProps(title=\"My books\")\n
Here you will see we have updated the Flask view function with a return type, our HomePageProps
, that we wrote earlier.
We have also updated the return line to return an instance of that class.
The one line of code that makes this all magically generate the files is beckett.page()
. This decorator inspects all the associated pages when the server is in development mode, and if it can't find a related TypeScript file for it, it will generate it for you.
Here are the files it has generated for us:
src/js/template/books/home.tsx
src/js/template/books.home.type.ts
Notice how Beckett has intelligently figured out the directory and the file name from the name of the python file and the view function.
Note
Sometimes when a new file is generated you need to run make web
again for the page to load - this is only a problem the first time and we're working to fix that bug.
If you refresh your browser at http://localhost:9000/books/ you should see a react page with the default template information from src/js/template/books/home.tsx
.
React is most useful when we can make concurrent API requests to our application server and return content.
Beckett manages API views that we can be used in our React pages. Let's make one now.
Add this just the home
view function:
src/views/books.py
from src.beckett.types import APIResponse\nclass BookResponse(APIResponse):\nname: str\nauthor: str\n@beckett.api_get(\"/details\")\ndef details() -> BookResponse:\nreturn BookResponse(name=\"Gideon the Ninth\", author=\"Tamsyn Muir.\")\n
Hit save and you should see some files change again (that happens a lot with Beckett). Let's go over this change quickly. from src.beckett.types import APIResponse\n
All of Beckett's API Views must return a subclass of APIResponse
. There are a few generic helpers in src.beckett.types
too if you want to return common responses like Forbidden
or NotFound
.
class BookResponse(APIResponse):\nname: str\nauthor: str\n
The BookResponse
is the response class we're using for our API. You'll see in src/js/api/types.ts
that this gets transformed into a TypeScript interface:
export interface BooksDetailsResponse {\n\"status_code\": number\n\"name\": string\n\"author\": string\n}\n
This all happens because of the decorator function beckett.api_get
:
@beckett.api_get(\"/details\")\ndef details() -> BookResponse:\nreturn BookResponse(name=\"Gideon the Ninth\", author=\"Tamsyn Muir.\")\n
Similar to beckett.api
- this decorator will inspect the view functions when in development mode, and generate appropriate TypeScript interfaces for you.
This interface is available in our TypeScript code now, but we don't acutally need to plug it into our APIClient, because Beckett has done that already for us!
Have a look further down the types.ts
file:
export interface GET_MAP {\n// beckett_framework/src/views/books.py\n\"books.details\": {request: undefined, response: BooksDetailsResponse}\n// beckett_framework/src/views/people.py\n\"people.get_people\": {request: undefined, response: PeopleGetPeopleResponse}\n}\n
The GET_MAP
has updated to include the request (currently none) and response types for this API endpoint. So how do we use it?
Let's write some typescript shall we?
Open up src/js/template/books/home.tsx
and make these changes:
import React from 'react'\nimport PageProps from './home.type'\nimport {Container, Row} from 'react-bootstrap'\nimport { useGet } from '~/api/query'\nconst Page: React.FunctionComponent<PageProps> = props => {\nconst {data} = useGet(\"books.details\")\nreturn (\n<Container>\n<Row className=\"mb-4 border-bottom\">\n<h1>Hello, React!</h1>\n<p>\nHere are your props:{' '}\n<code>\n{Object.keys(props).map(p => (\n<p>\n{p} : {props[p]}\n</p>\n))}\n</code>\n</p>\n<p>\nBook name: {data.name}\n</p>\n<p>\nBook author: {data.author}\n</p>\n</Row>\n</Container>\n)\n}\nexport default Page\n
Let's go over these changes so they make sense.
import { useGet } from '~/api/query'\n
useGet
is a React Hook that provides access to all the registered API views in our Beckett application.
It uses the GET_MAP
to make sure the request and responses remain consistent.
const {data} = useGet(\"books.details\")\n
We do not need to provide any inputs to the useGet
function except for the API view we want to call - in this case it will always be the file_name.view_function
pattern. In fact, if you typed the example out letter by letter you would've noticed that it provided autocomplete!
Because Beckett's React pages use React suspense - it gracefully handles the loading state for you. This means that the page will not render until the API call has returned a response.
<p>\nBook name: {data.name}\n</p>\n<p>\nBook author: {data.author}\n</p>\n
Because we don't need to worry about the loading state, we can safely apply the values from the API call into our React page.
"},{"location":"tutorial/#apis-with-input-parameters","title":"APIs with input parameters","text":"Let's grow our API functionality even more by making it require an input value. We'll update the API View first:
src/views/books.py@beckett.api_get(\"/details\")\ndef details(name: str) -> BookResponse:\nreturn BookResponse(name=name, author=\"Tamsyn Muir.\")\n
The only thing we've changed here is we've added an input parameter name
, which is a str
, and then returned it in our API Response. This is just a simple demonstrative example, but you could imagine making database queries or doing some complex maths here - it's just Python after all!
When you save this, you'll notice a new TypeScript interface will be generated in src/js/api/types.ts
:
export interface BooksDetailsRequest {\n\"name\": string\n}\n
This new request interface will be mapped to the GET_MAP
in the same file:
export interface GET_MAP {\n// beckett_framework/src/views/books.py\n\"books.details\": {request: BooksDetailsRequest, response: BooksDetailsResponse}\n// beckett_framework/src/views/people.py\n\"people.get_people\": {request: undefined, response: PeopleGetPeopleResponse}\n}\n
In fact, if you try and refresh the browser at http://localhost:9000/books/ you'll see this take affect immediately - the page will crash! This is because the name
value in the request is a required parameter.
Let's update our React app so we can send input data to our API Client.
src/js/template/books/home.tsximport React from 'react'\nimport PageProps from './home.type'\nimport {Container, Row} from 'react-bootstrap'\nimport { useGet } from '~/api/query'\nconst Page: React.FunctionComponent<PageProps> = props => {\nconst {data} = useGet(\"books.details\", {name: \"My book\"})\n...\n
For the sake of brevity we've removed a bunch of this code in the tutorial. The only change needed is to pass an object with the required parameters to the API and then the page should load fine. You'll notice as you type of the object it will give you hints as to the expected input parameters.
Magic, eh?
"},{"location":"tutorial/#api-post-requests","title":"API Post requests","text":"Now that we've built an API that accepts data and returns it, let's build something more functional. How about an HTTP POST API View?
What's the difference between a POST view and a GET view in Beckett?
First things first, let's add a new View to our API:
src/views/books.pyclass BookUpdateResponse(APIResponse):\nname: str\nauthor: str\n@beckett.api_post(\"/add\")\ndef add(name: str, author: str) -> BookUpdateResponse:\n# Imagine database persistence happens here\nreturn BookUpdateResponse(name=name, author=author)\n
This should all be looking very familiar now. The one small difference from the previous API is we are using beckett.api_post
instead of beckett.api_get
.
Just like before, this will have generated two new interfaces for us:
src/js/api/types.tsexport interface BooksAddRequest {\n\"name\": string\n\"author\": string\n}\nexport interface BooksAddResponse {\n\"status_code\": number\n\"name\": string\n\"author\": string\n}\n
You will also see the POST_MAP
will have been updated:
export interface POST_MAP {\n// beckett_framework/src/views/books.py\n\"books.add\": {request: BooksAddRequest, response: BooksAddResponse}\n// beckett_framework/src/views/people.py\n\"people.post_example\": {request: PeoplePostExampleRequest, response: PeoplePostExampleResponse}\n}\n
Similar to the GET_MAP
, the POST_MAP
allows us to manage the API Client nicely.
Let's add a form that uses this API to our home.tsx
file:
import React, {useState} from 'react'\nimport PageProps from './home.type'\nimport {Container, Row} from 'react-bootstrap'\nimport {useGet, usePost} from '~/api/query'\nconst Page: React.FunctionComponent<PageProps> = props => {\nconst {data} = useGet('books.details', {name: 'My book'})\nconst addBook = usePost('books.add')\ninterface Book {\nname: string\nauthor: string\n}\nconst [allBooks, setAllBooks] = useState<Book[]>([{name: 'First book', author: 'No one'}])\nconst [formName, setName] = useState<string>('')\nconst [formAuthor, setAuthor] = useState<string>('')\nreturn (\n<Container>\n<Row className=\"mb-4 border-bottom\">\n<h1>Hello, React!</h1>\n<p>\nHere are your props:{' '}\n<code>\n{Object.keys(props).map(p => (\n<p>\n{p} : {props[p]}\n</p>\n))}\n</code>\n</p>\n<p>Book name: {data.name}</p>\n<p>Book author: {data.author}</p>\n</Row>\n<Row>\n<h1>Add book</h1>\n<p>\nName: <input value={formName} onChange={v => setName(v.target.value)} />\n</p>\n<p>\nAuthor: <input value={formAuthor} onChange={v => setAuthor(v.target.value)} />\n</p>\n<button\nonClick={() =>\naddBook.mutate(\n{name: formName, author: formAuthor},\n{\nonSuccess: response => {\nallBooks?.push({name: response.name, author: response.author})\nsetAllBooks(Array.from(allBooks))\n},\nonError: () => console.log(addBook.error),\n},\n)\n}\n>\nAdd\n</button>\n</Row>\n<Row>\n<ul>\n{allBooks.map(book => <li>{book.name} by {book.author}</li>)}\n</ul>\n</Row>\n</Container>\n)\n}\nexport default Page\n
I appreciate that quite a lot of lines have been added here. This may look a bit intimdating if you're not used to React but don't worry it'll all make sense.
Let's break down each section bit by bit.
const addBook = usePost('books.add')\n
The usePost
hook behaves very differently to the useGet
hook. This is because we often want to trigger to hook after a state change or button press.
Under the hood, the usePost
hook provides a TanStack Query hook for managing the API request.
interface Book {\nname: string\nauthor: string\n}\nconst [allBooks, setAllBooks] = useState<Book[]>([{name: 'First book', author: 'No one'}])\nconst [formName, setName] = useState<string>('')\nconst [formAuthor, setAuthor] = useState<string>('')\n
This code sets ups some state using React's state library. We'll use it for the form and the list on the page.
<button\nonClick={() =>\naddBook.mutate(\n{name: formName, author: formAuthor},\n{\nonSuccess: response => {\nallBooks?.push({name: response.name, author: response.author})\nsetAllBooks(Array.from(allBooks))\n},\nonError: () => console.log(addBook.error),\n},\n)\n}\n>\nAdd\n</button>\n
The button
logic is the most interesting part of this change - it is where we trigger our POST API request!
The mutate method is the standard TanStack one, but Beckett forces the input data to match the BookAddRequest
interface it generated earlier.
The rest of this code follows the standard useMutation
side effects from Tanstack which you can read about here.
When you read the page at http://localhost:9000/books/ you will notice the new form is available. If you enter some content in it and hit \"Add\" - the UI updates, but if you open your network tab in the developer console (right click -> Inspect -> Network) you will see API requests being issued to the Flask server when you click the button.
Want to see how the input validation works in real time? Set the input fields to be empty and hit enter, and you will see some API errors in the console tab.
"},{"location":"tutorial/#refreshing-requests","title":"Refreshing requests","text":"If you want to refresh a request you can do this using the useInvalidate
hook:
const invalidate = useInvalidate()\n<button onClick={\n() => invalidate('books.details', {name: 'New book'})\n}>Update book</button>\n
"}]}
\ No newline at end of file
+{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"\ud83d\udcab Beckett Framework","text":""},{"location":"#build-type-safe-python-and-typescript-applications","title":"Build type safe Python and TypeScript applications","text":"Welcome to Beckett, a strongly-linked Flask and React application template.
Beckett combines a Flask server, with a React TypeScript UI into a comprehensive full-stack framework for building modern web applications.
Beckett features a sophisticated types manager that automatically synchronizes Python Type hints and TypeScript interfaces. This means that as you make changes to your API code in the server, Beckett diligently keeps the API Client up to date.
"},{"location":"#built-on-popular-tools","title":"Built on popular tools","text":"We use cutting edge Python tools including:
And our TypeScript is modern too:
Both TypeScript and Python hold their positions as two of the most widely used programming languages globally. As a result, they are frequently combined in various projects.
However, setting up a smooth and efficient development environment that integrates these languages can be a cumbersome and time-consuming process, often leading to a subpar developer experience. Thankfully, Beckett steps in to solve this challenge by through it's sophisticated types manager.
"},{"location":"#type-safe-productivity-boost","title":"Type-safe productivity boost","text":"Beckett provides:
Wanna learn more? Start with the install step to get started.
"},{"location":"install/","title":"\ud83c\udfd7\ufe0f Install","text":""},{"location":"install/#early-days","title":"Early days","text":"Before this becomes a fully fledged package, this project exists as a template you can fork and build from.
Once forked, all you have to do is run this command:
make build\n
Then finally, you can either run both the Flask and Typescript server in one terminal with:
make dev\n
Or run them independently with:
make serve\nmake web\n
This will start up the development server.
Now you can start the tutorial to build your first Beckett page.
"},{"location":"todo/","title":"\ud83d\udccb TODO","text":"There is a bunch of stuff I want to add to Beckett, feel free to contribute!
meta
class to the PageProps
so things like the html <title>
tag can be updated, etc.make
commands to a beckett
command line tool.Before this becomes a fully fledged package, this project exists as a template you can fork and build from. My advice for getting started is to build ontop of what already exists, and use the existing routes until you understand it, then you can delete it!
Note
This example will work you through making a simple Bookshop app and API.
"},{"location":"tutorial/#add-your-first-view","title":"Add your first view","text":"In src.views
add a new file called books.py
and add the following code:
from src.app import app\nfrom src.beckett.blueprint import BeckettBlueprint\nbeckett = BeckettBlueprint(\"books\", __name__, url_prefix=\"/books\")\n@beckett.route(\"/\")\ndef home():\nreturn dict(title=\"My books\")\napp.register_blueprint(beckett)\n
Then in src.views.__init__
we just need to do a small update as seen on line 2:
from src.views import people, index # noqa\nfrom .books import *\n
This small import will let the Flask/BeckettApp know to include your views file in the application.
At this point you can now visit http://localhost:9000/books/ to see a simple API response.
"},{"location":"tutorial/#turn-it-into-a-react-page","title":"Turn it into a React page","text":"Let's make this simple API response become something far more interesting - a whole react page!
We only need to make a few changes:
src/views/books.pyfrom src.app import app\nfrom src.beckett.blueprint import BeckettBlueprint\nfrom src.beckett.types import PageProps\nbeckett = BeckettBlueprint(\"books\", __name__, url_prefix=\"/books\")\nclass HomePageProps(PageProps):\ntitle: str\n@beckett.route(\"/\")\n@beckett.page()\ndef home() -> HomePageProps:\nreturn HomePageProps(title=\"My books\")\napp.register_blueprint(beckett)\n
Quite a few interesting things should happen when you save the file - you'll see two brand new files appear in the src/js/templates
directory. I know you want to inspect them and see what has changed, but for now let us go over the changes in the books.py
file first:
from src.beckett.types import PageProps\n
The PageProps
baseclass should be used for all React pages.
class HomePageProps(PageProps):\ntitle: str\n
This is the response type of your React page, and will be transformed into an equivalent Typescript interface. You can inspect the sibling interface now in src/js/template/books/home.type.ts
:
export default interface PageProps {\n\"title\": string\n}\n
Notice how the name of the interface has changed? This is because each React page in Beckett only has one set of initial props for a page load, and this naming convention helps to pick it out.
@beckett.page()\ndef home() -> HomePageProps:\nreturn HomePageProps(title=\"My books\")\n
Here you will see we have updated the Flask view function with a return type, our HomePageProps
, that we wrote earlier.
We have also updated the return line to return an instance of that class.
The one line of code that makes this all magically generate the files is beckett.page()
. This decorator inspects all the associated pages when the server is in development mode, and if it can't find a related TypeScript file for it, it will generate it for you.
Here are the files it has generated for us:
src/js/template/books/home.tsx
src/js/template/books.home.type.ts
Notice how Beckett has intelligently figured out the directory and the file name from the name of the python file and the view function.
Note
Sometimes when a new file is generated you need to run make web
again for the page to load - this is only a problem the first time and we're working to fix that bug.
If you refresh your browser at http://localhost:9000/books/ you should see a react page with the default template information from src/js/template/books/home.tsx
.
React is most useful when we can make concurrent API requests to our application server and return content.
Beckett manages API views that we can be used in our React pages. Let's make one now.
Add this just the home
view function:
src/views/books.py
from src.beckett.types import APIResponse\nclass BookResponse(APIResponse):\nname: str\nauthor: str\n@beckett.api_get(\"/details\")\ndef details() -> BookResponse:\nreturn BookResponse(name=\"Gideon the Ninth\", author=\"Tamsyn Muir.\")\n
Hit save and you should see some files change again (that happens a lot with Beckett). Let's go over this change quickly. from src.beckett.types import APIResponse\n
All of Beckett's API Views must return a subclass of APIResponse
. There are a few generic helpers in src.beckett.types
too if you want to return common responses like Forbidden
or NotFound
.
class BookResponse(APIResponse):\nname: str\nauthor: str\n
The BookResponse
is the response class we're using for our API. You'll see in src/js/api/types.ts
that this gets transformed into a TypeScript interface:
export interface BooksDetailsResponse {\n\"status_code\": number\n\"name\": string\n\"author\": string\n}\n
This all happens because of the decorator function beckett.api_get
:
@beckett.api_get(\"/details\")\ndef details() -> BookResponse:\nreturn BookResponse(name=\"Gideon the Ninth\", author=\"Tamsyn Muir.\")\n
Similar to beckett.api
- this decorator will inspect the view functions when in development mode, and generate appropriate TypeScript interfaces for you.
This interface is available in our TypeScript code now, but we don't acutally need to plug it into our APIClient, because Beckett has done that already for us!
Have a look further down the types.ts
file:
export interface GET_MAP {\n// beckett_framework/src/views/books.py\n\"books.details\": {request: undefined, response: BooksDetailsResponse}\n// beckett_framework/src/views/people.py\n\"people.get_people\": {request: undefined, response: PeopleGetPeopleResponse}\n}\n
The GET_MAP
has updated to include the request (currently none) and response types for this API endpoint. So how do we use it?
Let's write some typescript shall we?
Open up src/js/template/books/home.tsx
and make these changes:
import React from 'react'\nimport PageProps from './home.type'\nimport {Container, Row} from 'react-bootstrap'\nimport { useGet } from '~/api/query'\nconst Page: React.FunctionComponent<PageProps> = props => {\nconst {data} = useGet(\"books.details\")\nreturn (\n<Container>\n<Row className=\"mb-4 border-bottom\">\n<h1>Hello, React!</h1>\n<p>\nHere are your props:{' '}\n<code>\n{Object.keys(props).map(p => (\n<p>\n{p} : {props[p]}\n</p>\n))}\n</code>\n</p>\n<p>\nBook name: {data.name}\n</p>\n<p>\nBook author: {data.author}\n</p>\n</Row>\n</Container>\n)\n}\nexport default Page\n
Let's go over these changes so they make sense.
import { useGet } from '~/api/query'\n
useGet
is a React Hook that provides access to all the registered API views in our Beckett application.
It uses the GET_MAP
to make sure the request and responses remain consistent.
const {data} = useGet(\"books.details\")\n
We do not need to provide any inputs to the useGet
function except for the API view we want to call - in this case it will always be the file_name.view_function
pattern. In fact, if you typed the example out letter by letter you would've noticed that it provided autocomplete!
Because Beckett's React pages use React suspense - it gracefully handles the loading state for you. This means that the page will not render until the API call has returned a response.
<p>\nBook name: {data.name}\n</p>\n<p>\nBook author: {data.author}\n</p>\n
Because we don't need to worry about the loading state, we can safely apply the values from the API call into our React page.
"},{"location":"tutorial/#apis-with-input-parameters","title":"APIs with input parameters","text":"Let's grow our API functionality even more by making it require an input value. We'll update the API View first:
src/views/books.py@beckett.api_get(\"/details\")\ndef details(name: str) -> BookResponse:\nreturn BookResponse(name=name, author=\"Tamsyn Muir.\")\n
The only thing we've changed here is we've added an input parameter name
, which is a str
, and then returned it in our API Response. This is just a simple demonstrative example, but you could imagine making database queries or doing some complex maths here - it's just Python after all!
When you save this, you'll notice a new TypeScript interface will be generated in src/js/api/types.ts
:
export interface BooksDetailsRequest {\n\"name\": string\n}\n
This new request interface will be mapped to the GET_MAP
in the same file:
export interface GET_MAP {\n// beckett_framework/src/views/books.py\n\"books.details\": {request: BooksDetailsRequest, response: BooksDetailsResponse}\n// beckett_framework/src/views/people.py\n\"people.get_people\": {request: undefined, response: PeopleGetPeopleResponse}\n}\n
In fact, if you try and refresh the browser at http://localhost:9000/books/ you'll see this take affect immediately - the page will crash! This is because the name
value in the request is a required parameter.
Let's update our React app so we can send input data to our API Client.
src/js/template/books/home.tsximport React from 'react'\nimport PageProps from './home.type'\nimport {Container, Row} from 'react-bootstrap'\nimport { useGet } from '~/api/query'\nconst Page: React.FunctionComponent<PageProps> = props => {\nconst {data} = useGet(\"books.details\", {name: \"My book\"})\n...\n
For the sake of brevity we've removed a bunch of this code in the tutorial. The only change needed is to pass an object with the required parameters to the API and then the page should load fine. You'll notice as you type of the object it will give you hints as to the expected input parameters.
Magic, eh?
"},{"location":"tutorial/#api-post-requests","title":"API Post requests","text":"Now that we've built an API that accepts data and returns it, let's build something more functional. How about an HTTP POST API View?
What's the difference between a POST view and a GET view in Beckett?
First things first, let's add a new View to our API:
src/views/books.pyclass BookUpdateResponse(APIResponse):\nname: str\nauthor: str\n@beckett.api_post(\"/add\")\ndef add(name: str, author: str) -> BookUpdateResponse:\n# Imagine database persistence happens here\nreturn BookUpdateResponse(name=name, author=author)\n
This should all be looking very familiar now. The one small difference from the previous API is we are using beckett.api_post
instead of beckett.api_get
.
Just like before, this will have generated two new interfaces for us:
src/js/api/types.tsexport interface BooksAddRequest {\n\"name\": string\n\"author\": string\n}\nexport interface BooksAddResponse {\n\"status_code\": number\n\"name\": string\n\"author\": string\n}\n
You will also see the POST_MAP
will have been updated:
export interface POST_MAP {\n// beckett_framework/src/views/books.py\n\"books.add\": {request: BooksAddRequest, response: BooksAddResponse}\n// beckett_framework/src/views/people.py\n\"people.post_example\": {request: PeoplePostExampleRequest, response: PeoplePostExampleResponse}\n}\n
Similar to the GET_MAP
, the POST_MAP
allows us to manage the API Client nicely.
Let's add a form that uses this API to our home.tsx
file:
import React, {useState} from 'react'\nimport PageProps from './home.type'\nimport {Container, Row} from 'react-bootstrap'\nimport {useGet, usePost} from '~/api/query'\nconst Page: React.FunctionComponent<PageProps> = props => {\nconst {data} = useGet('books.details', {name: 'My book'})\nconst addBook = usePost('books.add')\ninterface Book {\nname: string\nauthor: string\n}\nconst [allBooks, setAllBooks] = useState<Book[]>([{name: 'First book', author: 'No one'}])\nconst [formName, setName] = useState<string>('')\nconst [formAuthor, setAuthor] = useState<string>('')\nreturn (\n<Container>\n<Row className=\"mb-4 border-bottom\">\n<h1>Hello, React!</h1>\n<p>\nHere are your props:{' '}\n<code>\n{Object.keys(props).map(p => (\n<p>\n{p} : {props[p]}\n</p>\n))}\n</code>\n</p>\n<p>Book name: {data.name}</p>\n<p>Book author: {data.author}</p>\n</Row>\n<Row>\n<h1>Add book</h1>\n<p>\nName: <input value={formName} onChange={v => setName(v.target.value)} />\n</p>\n<p>\nAuthor: <input value={formAuthor} onChange={v => setAuthor(v.target.value)} />\n</p>\n<button\nonClick={() =>\naddBook.mutate(\n{name: formName, author: formAuthor},\n{\nonSuccess: response => {\nallBooks?.push({name: response.name, author: response.author})\nsetAllBooks(Array.from(allBooks))\n},\nonError: () => console.log(addBook.error),\n},\n)\n}\n>\nAdd\n</button>\n</Row>\n<Row>\n<ul>\n{allBooks.map(book => <li>{book.name} by {book.author}</li>)}\n</ul>\n</Row>\n</Container>\n)\n}\nexport default Page\n
I appreciate that quite a lot of lines have been added here. This may look a bit intimdating if you're not used to React but don't worry it'll all make sense.
Let's break down each section bit by bit.
const addBook = usePost('books.add')\n
The usePost
hook behaves very differently to the useGet
hook. This is because we often want to trigger to hook after a state change or button press.
Under the hood, the usePost
hook provides a TanStack Query hook for managing the API request.
interface Book {\nname: string\nauthor: string\n}\nconst [allBooks, setAllBooks] = useState<Book[]>([{name: 'First book', author: 'No one'}])\nconst [formName, setName] = useState<string>('')\nconst [formAuthor, setAuthor] = useState<string>('')\n
This code sets ups some state using React's state library. We'll use it for the form and the list on the page.
<button\nonClick={() =>\naddBook.mutate(\n{name: formName, author: formAuthor},\n{\nonSuccess: response => {\nallBooks?.push({name: response.name, author: response.author})\nsetAllBooks(Array.from(allBooks))\n},\nonError: () => console.log(addBook.error),\n},\n)\n}\n>\nAdd\n</button>\n
The button
logic is the most interesting part of this change - it is where we trigger our POST API request!
The mutate method is the standard TanStack one, but Beckett forces the input data to match the BookAddRequest
interface it generated earlier.
The rest of this code follows the standard useMutation
side effects from Tanstack which you can read about here.
When you read the page at http://localhost:9000/books/ you will notice the new form is available. If you enter some content in it and hit \"Add\" - the UI updates, but if you open your network tab in the developer console (right click -> Inspect -> Network) you will see API requests being issued to the Flask server when you click the button.
Want to see how the input validation works in real time? Set the input fields to be empty and hit enter, and you will see some API errors in the console tab.
"},{"location":"tutorial/#refreshing-requests","title":"Refreshing requests","text":"If you want to refresh a request you can do this using the useInvalidate
hook:
const invalidate = useInvalidate()\n<button onClick={\n() => invalidate('books.details', {name: 'New book'})\n}>Update book</button>\n
"}]}
\ No newline at end of file
diff --git a/sitemap.xml.gz b/sitemap.xml.gz
index 0d860f2..fddb7cf 100644
Binary files a/sitemap.xml.gz and b/sitemap.xml.gz differ