{"id":20957019,"url":"https://github.com/spilth/annotated-spring-episode-004","last_synced_at":"2025-10-28T11:18:23.654Z","repository":{"id":136684035,"uuid":"42968291","full_name":"spilth/annotated-spring-episode-004","owner":"spilth","description":"CRUD Web App: Part 1 - Create \u0026 Read","archived":false,"fork":false,"pushed_at":"2015-09-23T00:12:56.000Z","size":112,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-01-19T23:44:37.931Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://www.annotatedspring.com/episodes/4/crud-web-app-part-1---create-and-read","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/spilth.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-09-23T00:12:17.000Z","updated_at":"2019-05-18T09:37:12.000Z","dependencies_parsed_at":"2023-03-13T09:11:10.574Z","dependency_job_id":null,"html_url":"https://github.com/spilth/annotated-spring-episode-004","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spilth%2Fannotated-spring-episode-004","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spilth%2Fannotated-spring-episode-004/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spilth%2Fannotated-spring-episode-004/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spilth%2Fannotated-spring-episode-004/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/spilth","download_url":"https://codeload.github.com/spilth/annotated-spring-episode-004/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243352033,"owners_count":20276916,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-19T01:29:16.552Z","updated_at":"2025-10-28T11:18:23.575Z","avatar_url":"https://github.com/spilth.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# CRUD Web App: Part 1 - Create \u0026 Read\n\n## Introduction\n\nWe're going to build a simple note taking web app that lets us create, view, list and edit notes written using the popular [Markdown](http://daringfireball.net/projects/markdown/) syntax. In this first part we're going to implement Creating and Reading. In a future episode we'll add Editing and deletion.\n\n## Project Initialization\n\nWe start by creating a Spring Boot web app with the 'data-jpa', 'freemarker' and 'h2' dependencies.\n\n    $ spring init crud -d=web,data-jpa,freemarker,h2\n\nWe'll be using [Spring Data JPA](http://projects.spring.io/spring-data-jpa/) to help us create a Note model that can be easily persisted and queried.\n\nTo keep things simple we're going to use the [H2 in-memory database engine](http://www.h2database.com/) so that we can focus on just the model, controller and view. In a future episode we'll configure the application to use MySQL instead.\n\nThe template engine we'll be using is [Freemarker](http://freemarker.org).\n\n## CRUD URL Convention\n\nWe're going to follow [a popular convention for the URLs and HTTP verbs that we'll use](http://microformats.org/wiki/rest/urls) in our controller:\n\n| Path             | HTTP Verb | Controller Method | Purpose                                  |\n|------------------|-----------|-------------------|------------------------------------------|\n| /notes           | GET       | index             | List all Notes                           |\n| /notes/new       | GET       | new               | Render form for creating a new Note      |\n| /notes           | POST      | create            | Create a new Note                        |\n| /notes/{id}      | GET       | show              | Display a specific Note                  |\n\n- We list all notes by GETting `/notes`\n- We load the form for a new note by GETting `/notes/new`\n- We create a new note by POSTing a note's attributes to `/notes`\n- We show a note by GETtting `/notes/{id}`, where `id` is its unique identifier - generally a primary key in the model's database table\n\n## Main Page\n\nTo start, we’re going to make a page that will eventually list all our notes. But since we don't have any to list yet, we'll just have a button that lets us make a new note.\n\nFirst we make a controller for our notes with an index method that is mapped to `/notes`:\n\n    @Controller\n    public class NotesController {\n        @RequestMapping(\"/notes\", RequestMethod = GET)\n        public String notesIndex() {\n            return \"notes/index\";\n        }\n    }\n\nNow we make the template `index.ftl` inside `src/resources/templates/notes/`:\n\n    \u003chtml\u003e\n        \u003chead\u003e\n            \u003ctitle\u003eNotes\u003c/title\u003e\n        \u003c/head\u003e\n\n        \u003cbody\u003e\n            \u003ch1\u003eNotes\u003c/h1\u003e\n\n            \u003cp\u003e\u003ca href=\"/notes/new\"\u003eNew Note\u003c/a\u003e\u003c/p\u003e\n        \u003c/body\u003e\n    \u003c/html\u003e\n\nLet's add a controller method to handle the path in our link: `/notes/new`.\n\n    @RequestMapping(\"/notes/new\")\n    public String notesNew() {\n        return \"notes/new\";\n    }\n\nThen inside `src/resources/templates/notes/`, create `new.ftl` with a form for entering a Note's title and contents:\n\n    \u003chtml\u003e\n        \u003chead\u003e\n            \u003ctitle\u003eNew Note\u003c/title\u003e\n        \u003c/head\u003e\n\n        \u003cbody\u003e\n            \u003ch1\u003eNew Note\u003c/h1\u003e\n\n            \u003cform action=\"/notes\" method=\"POST\"\u003e\n                \u003cdl\u003e\n                    \u003cdt\u003eTitle\u003c/dt\u003e\n                    \u003cdd\u003e\n                        \u003cinput type=\"text\" name=\"title\"\u003e\n                    \u003c/dd\u003e\n\n                    \u003cdt\u003eContent\u003c/dt\u003e\n                    \u003cdd\u003e\n                        \u003ctextarea type=\"text\" name=\"content\" rows=\"8\" cols=\"80\"\u003e\u003c/textarea\u003e\n                    \u003c/dd\u003e\n                    \u003cdt\u003e\u003cinput type=\"submit\" value=\"Create\" /\u003e\u003c/dt\u003e\n                \u003c/dl\u003e\n            \u003c/form\u003e\n        \u003c/body\u003e\n    \u003c/html\u003e\n\nIn order to save the Note, we'll need two things: a class that represents a Note and a repository that handles saving Notes.\n\nThe Note model will have 3 fields: id, title and content. We also need to add getters and setters for each field.\n\n    public class Note {\n        private Integer id;\n\n        private String title;\n\n        private String content;\n\n        public Integer getId() {\n            return id;\n        }\n\n        public void setId(Integer id) {\n            this.id = id;\n        }\n\n        public String getTitle() {\n            return title;\n        }\n\n        public void setTitle(String title) {\n            this.title = title;\n        }\n\n        public String getContent() {\n            return content;\n        }\n\n        public void setContent(String content) {\n            this.content = content;\n        }\n    }\n\nSpring Data JPA provides a `CrudRepository` class that we can extend to easily create a repository for our Notes. We pass it the class we want to store and the type we are using for the `id` field.  This interface will be picked up by Spring and automatically have methods for finding and saving Notes.\n\n    public interface NotesRepository extends CrudRepository\u003cNote, Integer\u003e{}\n\nWhen you click the submit button the form will `POST` to `/notes`. Let's add a controller method to handle that.\n\n    @Controller\n    public class NotesController {\n        @Autowired\n        private NotesRepository notesRepository;\n\n        @RequestMapping(value = \"/notes\", method = POST)\n        public String notesCreate(Note note) {\n            note = notesRepository.save(note);\n\n            return \"redirect:/notes/\" + note.getId();\n        }\n    }\n\nThe last line of the create method redirects the user to the show page of the Note once it has been created. We'll need one last controller method to handle that.\n\n    @RequestMapping(value = \"/notes/{noteId}\", method = GET)\n    public String notesShow(@PathVariable(\"noteId\") Integer noteId, Model model) {\n        Note note = notesRepository.findOne(noteId);\n        model.addAttribute(\"note\", note);\n\n        return \"notes/show\";\n    }\n\nHere we are passing the `id` of the note in the path to the method. Then inside the method we can use the repository to find the requested note. Finaly, we assign this note to a model that is automatically made available to the view template.\n\nWe create `show.ftl` inside of `src/resouces/templates/notes/` and give if the following contents:\n\n    \u003chtml\u003e\n        \u003chead\u003e\n            \u003ctitle\u003e${note.title}\u003c/title\u003e\n        \u003c/head\u003e\n\n        \u003cbody\u003e\n            \u003ch1\u003e${note.title}\u003c/h1\u003e\n\n            \u003cp\u003e${note.content}\u003c/p\u003e\n\n            \u003cp\u003e\u003ca href=\"/notes/\"\u003eAll Notes\u003c/a\u003e\u003c/p\u003e\n        \u003c/body\u003e\n    \u003c/html\u003e\n\nIf we start the web application, we get an error during startup:\n\n    Error creating bean with name 'notesRepository': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Not an managed type: class demo.notes.Note\n\nIt's complaining that our class Note is `Not an managed type`.\n\nWe need to put some annotations on the Note class so it knows that it's a model we can save. We add the `@Entity` annotation to the class to mark it as a Data entity. We then add the `@Id` annotation to the `id` field to mark it as the primary key for this class, along with `@GeneratedValue` to auto-populate the id on creation. Finally, we need to use the `@Lob` annotation to specify that the `content` field will hold a large amount of data. Otherwise we'd be limited to 255 characters by default.\n\n    @Entity\n    public class Note {\n        @Id\n        @GeneratedValue\n        private Integer id;\n\n        @Lob\n        private String content;\n\nIf we restart the server, we should be able to create a new note and view it. But there's still no way to still all the notes in the sytem.\n\n## List Notes Revisited\n\nWe need to update the index action to load all the Notes in the repository and expose that list to the view model:\n\n    @RequestMapping(value = \"/notes\", method = GET)\n    public String notesIndex(Model model) {\n        model.addAttribute(\"notes\", notesRepository.findAll());\n\n        return \"notes/index\";\n    }\n\nThen we can update `index.ftl` to loop through the notes and render a link to each one.\n\n    \u003cul\u003e\n        \u003c#list notes as note\u003e\n            \u003cli\u003e\u003ca href=\"/notes/${note.id}\"\u003e${note.title}\u003c/a\u003e\u003c/li\u003e\n        \u003c/#list\u003e\n    \u003c/ul\u003e\n\n## Adding Markdown Support to Notes\n\nThe last piece of functionality we need to add is supporting Markdown in the contents of our notes. There is a Java library named [pegdown](https://github.com/sirthias/pegdown) that helps us quickly accomplish this.\n\nFirst we add it as a dependency to our project in out `pom.xml`:\n\n    \u003cdependency\u003e\n        \u003cgroupId\u003eorg.pegdown\u003c/groupId\u003e\n        \u003cartifactId\u003epegdown\u003c/artifactId\u003e\n        \u003cversion\u003e1.5.0\u003c/version\u003e\n    \u003c/dependency\u003e\n\nThen we add a getter method in our Note class:\n\n    public String getContentHtml() {\n        return new PegDownProcessor().markdownToHtml(content);\n    }\n\nAnd call it from the show template:\n\n    \u003chtml\u003e\n        \u003chead\u003e\n            \u003ctitle\u003e${note.title}\u003c/title\u003e\n        \u003c/head\u003e\n\n        \u003cbody\u003e\n            \u003ch1\u003e${note.title}\u003c/h1\u003e\n\n            \u003cp\u003e${note.contentHtml}\u003c/p\u003e\n\n            \u003cp\u003e\u003ca href=\"/notes/\"\u003eAll Notes\u003c/a\u003e\u003c/p\u003e\n        \u003c/body\u003e\n    \u003c/html\u003e\n\nOnce again we restart our server, create a note, paste in some Markdown and voila - we can generate HTML from Markdown.\n\n## Exercise\n\n- Use what you learned in Episode 3 to apply a consistent look to the site using Bootstrap, WebJars and Freemarker Macros.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspilth%2Fannotated-spring-episode-004","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fspilth%2Fannotated-spring-episode-004","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspilth%2Fannotated-spring-episode-004/lists"}