ks7 is setting up the foundation for the next couple of episodes. Where we are going to start working with data and databases. This episode does not need any kubernetes changes, only code changes.
We are going to modify our backend and frontend code so that we can have a Todo application that stores data in memory. This will lay the groundwork for the next episodes.
To test or develop against ks7 go to section Set up and start ks7
-
Create a Todo list API in the backend server
We start by creating a simple API for Todo list operations:
Define the following url rules in our python server
server/server.py
app.add_url_rule('/api/todo/list', view_func=controller_todo.list_items, methods=['GET']) app.add_url_rule('/api/todo/add', view_func=controller_todo.add, methods=['POST']) app.add_url_rule('/api/todo/delete', view_func=controller_todo.delete, methods=['POST']) app.add_url_rule('/api/todo/item/update', view_func=controller_todo.item_update, methods=['POST'])
And we create a todo.py file in the
server/controllers
folder with the code for the API actions defined:todo.py
code:# in memory todo list storage todo_list = [] def list_items(): 'GET todo list' current_app.logger.info('todo controller called, func: list') return jsonify({ 'todoList': todo_list }) def add(): 'POST add item into todo list' current_app.logger.info('todo controller called, func: add') data = json.loads(request.data.decode("utf-8")) item = data['newItem'] todo_list.append(item) return jsonify({ 'todoList': todo_list }) def delete(): 'POST delete item from list' current_app.logger.info('todo controller called, func: delete') data = json.loads(request.data.decode("utf-8")) item = data['itemToDelete'] if item in todo_list: todo_list.remove(item) return jsonify({ 'todoList': todo_list }) def item_update(): 'POST update item in list' current_app.logger.info('todo controller called, func: item_update') data = json.loads(request.data.decode('utf-8')) item = data['itemToUpdate'] results = [x for x in todo_list if x['id'] == item['id']] if results: current_app.logger.info('found results') index = todo_list.index(results[0]) todo_list[index] = item return jsonify({ 'todoList': todo_list })
Notice that the
todo_list
is a python list defined as a global constant. This is not a good practice but it allows us create our todo list app without caring about how we are going to store things at the moment.In the next ks episode we will care about the right way to do this and modify the
server/controllers/todo.py
accordingly. -
create a
<AddTask>
component in the frontendThe
<AddTask>
component's responsibility is to allow user to submit a new task into the todo list.export class AddTask extends Component { constructor(props) { super(props) this.addTaskSubmit = this.addTaskSubmit.bind(this) } addTaskSubmit(e) { e.preventDefault() this.props.onTaskAdded(e.target.name.value) e.target.name.value = '' } render() { return <div className='add-task'> <form onSubmit={this.addTaskSubmit}> <input className='add-task-name' type='text' name='name' placeholder='What needs to be done?' size="30" /> </form> </div> } }
-
Create a
<TodoList>
componentThe
<TodoList>
component's responsibility is to display the todo list as well as allowing the user perform the following actions- remove an item from the list
- update an item from the list
export class TodoList extends Component { deleteTaskClick(itemId){ this.props.onTaskDeleted(itemId) } updateItemClick(item){ this.props.onTaskUpdate(item) } render() { return <div className='tasks'> {this.props.items.map(item => <div className='task-item' key={'item-' + item.id}> <div className='task-item-tick' title='click to do or undo a task' onClick={() => this.updateItemClick(item)}>{item.done? '☑': '☐'}</div> <div className='task-item-name'>{item.name}</div> <div className='task-item-delete' title='click to remove a task from your list' onClick={() => this.deleteTaskClick(item.id)}>x</div> </div> )} </div> } }
-
Use
<AddTask>
and<TodoList>
in<App>
renderOur
<App>
render function will look like thisrender() { return <div className='App'> <header className='App-header'> <h1 className='App-title'>ks7 app</h1> </header> <p className='App-intro'> ks7 message from web server: {this.state.message} </p> <AddTask onTaskAdded={this.onTaskAdded} /> <TodoList items={this.state.todoItems} onTaskUpdate={this.onTaskUpdate} onTaskDeleted={this.onTaskDeleted}/> </div> }
-
Fetch current items
For our app to display the items we first need to fetch them at the start of the
App
component lifecycle. To do that we call our backend server on/api/todo/list
in theApp
constructor.class App extends Component { constructor(props) { super(props) this.state = { message: 'moon', todoItems: [] } // ... this.fetchTodoItems() } fetchTodoItems(){ fetch('/api/todo/list', { 'Content': 'GET', headers: { 'Content-Type': 'application/json' } }).then(response => { return response.json() }).then(json => { this.setState({ todoItems: json.todoList}) }) }
-
handle
onTaskAdded
To do that we bind onTaskAdded in the constructor
this.onTaskAdded = this.onTaskAdded.bind(this)
And call the API
/api/todo/add
.onTaskAdded(taskName) { const newItem = { name: taskName, done: false, id: Date.now() } const payload = { newItem } fetch('/api/todo/add', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }).then(response => { return response.json() }).then(json => { this.setState({ todoItems: json.todoList}) }) }
-
handle
onTaskUpdate
Bind the function in the constructor:
this.onTaskUpdate = this.onTaskUpdate.bind(this)
Call the todo API on update
/api/todo/item/update
onTaskUpdate(item){ const itemToUpdate = this.state.todoItems.find(x => x.id === item.id) const copyItem = {...itemToUpdate} copyItem.done = !copyItem.done const payload = { itemToUpdate: copyItem } !!itemToUpdate && fetch('/api/todo/item/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }).then(response => { return response.json() }).then(json => { this.setState({ todoItems: json.todoList}) }) }
-
Handle
onTaskDelete
Bind the function in the constructor
this.onTaskDeleted = this.onTaskDeleted.bind(this)
Call the todo API update
/api/todo/item/delete
onTaskDeleted(taskId) { const itemToDelete = this.state.todoItems.find(x => x.id === taskId) const payload = { itemToDelete } !!itemToDelete && fetch('/api/todo/delete', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }).then(response => { return response.json() }).then(json => { this.setState({ todoItems: json.todoList}) }) }
-
start minikube and build docker images
➜ cd ks7 ➜ minikube start ➜ eval $(minikube docker-env) # Ensure you've built the react app first ➜ cd app ➜ yarn ➜ yarn build ➜ docker build -f ./server/Dockerfile -t ks7webserverimage . ➜ docker build -f ./web/Dockerfile -t ks7webimage .
-
mount volume
➜ cd ks7 ➜ minikube mount .:/mounted-ks7-src
-
install helm chart
➜ helm install ./ks -n ks
-
check app is working properly
➜ kubectl get pods NAME READY STATUS RESTARTS AGE ks-ks7web-7444588647-j8tmb 2/2 Running 0 30s
-
check logs
➜ kubectl logs ks-ks7web-7444588647-j8tmb ks7webfrontend ➜ kubectl logs ks-ks7web-7444588647-j8tmb ks7webserver
-
test app in browser
➜ minikube service ks-ks7web-service