{"componentChunkName":"component---src-templates-tag-jsx","path":"/blog/tag/community/3/","result":{"data":{"prismic":{"allFeaturedblogs":{"edges":[{"node":{"featured_blogs_enabled":true,"heading":[{"type":"paragraph","text":"Featured posts","spans":[]}],"featured_blog_1":{"__typename":"PRISMIC_Blog","_linkType":"Link.document","blog_header_image":{"dimensions":{"width":790,"height":395},"alt":null,"copyright":null,"url":"https://images.prismic.io/www-static/6d8d81b1-971a-4313-b033-b4e125cb14a0_MondoDB-blog-header-790x395.PNG?auto=compress,format"},"blog_headline":[{"type":"heading1","text":"Introducing DigitalOcean Managed MongoDB – a fully managed, database as a service for modern apps","spans":[]}],"blog_post_date":"2021-06-29","blog_post_content":[{"type":"paragraph","text":"MongoDB is one of the most popular databases, and it’s ideal for apps that evolve rapidly and need to handle huge volumes of data and traffic. It offers advantages like flexible document schemas, code-native data access, change-friendly design, and easy horizontal scale-out.","spans":[{"start":22,"end":44,"type":"hyperlink","data":{"link_type":"Web","url":"https://db-engines.com/en/ranking","target":"_blank"}}]},{"type":"paragraph","text":"However, building and maintaining MongoDB clusters from the ground up can be a huge undertaking. Developers often complain that they have to spend their valuable time and resources on database management. Well, we’ve been listening and have some great news: accessing and managing MongoDB on DigitalOcean just got a lot simpler!","spans":[]},{"type":"paragraph","text":"We are excited to announce that DigitalOcean Managed MongoDB is now in General Availability. Managed MongoDB is a fully managed, database as a service (DBaaS) offering from DigitalOcean, built in partnership with and certified by MongoDB Inc. It provides you all the technical capabilities that make MongoDB so beloved in the developer community. Together we have ensured that you will get access to all the latest releases of the MongoDB document database as they become available.","spans":[{"start":32,"end":91,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/products/managed-databases-mongodb/"}},{"start":230,"end":241,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.mongodb.com/","target":"_blank"}}]},{"type":"paragraph","text":"Managed MongoDB simplifies the MongoDB administration. Developers of all skill levels, even those who do not have prior experience in databases, can spin up MongoDB clusters in just a few minutes. We handle the provisioning, managing, scaling, updates, backups, and security of your MongoDB clusters, allowing you to offload the complex, time consuming –yet critical – database administration tasks to us. This empowers you to focus on what really matters: building awesome apps.","spans":[]},{"type":"embed","oembed":{"height":113,"width":200,"embed_url":"https://www.youtube.com/watch?v=NvHQSV7jnKA","type":"video","version":"1.0","title":"Create a MongoDB Database on DigitalOcean","author_name":"DigitalOcean","author_url":"https://www.youtube.com/c/Digitalocean","provider_name":"YouTube","provider_url":"https://www.youtube.com/","cache_age":null,"thumbnail_url":"https://i.ytimg.com/vi/NvHQSV7jnKA/hqdefault.jpg","thumbnail_width":480,"thumbnail_height":360,"html":"<iframe width=\"200\" height=\"113\" src=\"https://www.youtube.com/embed/NvHQSV7jnKA?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe>"}},{"type":"heading2","text":"Benefits of Managed MongoDB","spans":[]},{"type":"paragraph","text":"","spans":[]},{"type":"list-item","text":"Easy set up and maintenance: We create the database clusters for you. Simply choose the cluster configuration (e.g., memory, disk size, number of nodes, etc.), and the data center in which you want to host the database. Follow a few simple steps and your database cluster will be up and running in a matter of minutes. You can spin up clusters using the cloud control panel, CLI, or API.\n\n","spans":[{"start":0,"end":28,"type":"strong"}]},{"type":"list-item","text":"Automatic daily backups with point in time recovery: Data is one of the most important assets of an app, so it’s critical to backup your database. We take backups of your entire clusters automatically on a daily basis, for free. We also provide a point in time recovery for 7 days, that way if things go wrong due to human error, machine error, or some combination of both, you can easily restore the database as it was at any point in the previous 7 days. \n\n","spans":[{"start":0,"end":52,"type":"strong"}]},{"type":"list-item","text":"Automatic updates and access to latest MongoDB releases: You get access to MongoDB 4.4. This is the latest release of MongoDB and comes packed with numerous enhancements like hedged reads, rust, and swift drivers. Since we have developed Managed MongoDB in partnership with MongoDB Inc, you will always get access to new releases as they become available. With Managed MongoDB, the updates happen automatically. Just select a date and time for the updates and we take care of the rest. This makes it easy to stay up to date with MongoDB releases without disrupting your business.\n\n","spans":[{"start":0,"end":56,"type":"strong"},{"start":148,"end":169,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.mongodb.com/new","target":"_blank"}}]},{"type":"list-item","text":"High availability with automated failover: If your database goes down, it can take down the entire app, leading to bad customer experiences. With Managed MongoDB, you can easily minimize the downtime for your database and make it highly available with standby nodes. Standby nodes add redundancy, so if for example the primary node fails, the standby node is immediately promoted to primary and begins serving requests while we provision a replacement standby node in the background.\n\n","spans":[{"start":0,"end":42,"type":"strong"}]},{"type":"list-item","text":"Scale up easily to handle traffic spikes: As your app gains traction and the usage grows, it’s important to have a database that can keep up with the increased demand. With Managed MongoDB, you can easily scale up the size of database nodes when needed.\n\n","spans":[{"start":0,"end":41,"type":"strong"}]},{"type":"list-item","text":"Secure by default: Since data is critical, it also needs to be secure. We encrypt data at rest with LUKS and in transit with SSL. When you create a new cluster, it’s placed in a VPC network by default that provides a more secure connection between resources. You can also restrict access to your nodes to prevent brute-force password and denial-of-service attacks.","spans":[{"start":0,"end":18,"type":"strong"},{"start":178,"end":189,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/docs/networking/vpc/"}}]},{"type":"heading2","text":"The need for Managed Databases","spans":[]},{"type":"paragraph","text":"DigitalOcean’s mission is to simplify cloud computing so developers, startups, and SMBs can spend more time building software that changes the world. While databases are a critical component to any application, building, maintaining, and scaling them can be complex and time consuming. For developers that are building apps for their business, database administration is often not a core focus area. But it’s quite common to find developers that write the code and then also roll up their sleeves to maintain databases. Such users would rather offload the tedious database administration and focus their limited time and energy on building and enhancing their apps. ","spans":[]},{"type":"paragraph","text":"With this in mind, we introduced Managed Databases a couple of years ago and are excited to add Managed MongoDB to our portfolio. With this release, DigitalOcean Managed Databases now supports the following engines:","spans":[{"start":33,"end":50,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/products/managed-databases/"}}]},{"type":"image","url":"https://images.prismic.io/www-static/87745cc1-1c5f-4463-b104-104b7fc30dc7_managed-databases-logos.png?auto=compress,format","alt":null,"copyright":null,"dimensions":{"width":849,"height":104}},{"type":"paragraph","text":"Managed MongoDB launch comes on the heels of DigitalOcean App Platform, a modern, reimagined PaaS (Platform as a Service) that we released a few months ago. App Platform makes it very easy to build, deploy, and scale apps and static sites. You can deploy code by simply pointing to your GitHub and GitLab repos, and App Platform will do all the heavy lifting of managing infrastructure, app runtimes, and dependencies. App Platform, along with Managed Databases, helps fulfill DigitalOcean’s mission by empowering developers, startups, and SMBs to focus more on their apps, and less on the underlying infrastructure and databases.","spans":[{"start":45,"end":70,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/products/app-platform/"}}]},{"type":"heading2","text":"How Managed MongoDB works","spans":[]},{"type":"paragraph","text":"DigitalOcean provides you with various compute options to build your apps like:","spans":[]},{"type":"list-item","text":"Droplets: On-demand, Linux virtual machines suitable for production business applications and personal passion projects.","spans":[{"start":0,"end":8,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/products/droplets/"}}]},{"type":"list-item","text":"DigitalOcean Kubernetes: Managed Kubernetes with automatic scaling, upgrades, and a free control plane.","spans":[{"start":0,"end":23,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/products/kubernetes/"}}]},{"type":"list-item","text":"DigitalOcean App Platform: A fully managed Platform as a Service.","spans":[{"start":0,"end":25,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/products/app-platform/"}}]},{"type":"paragraph","text":"No matter which compute option you choose to build your apps, you can easily add Managed MongoDB to it. In addition to this, Managed MongoDB also integrates with the Node.js 1-Click App from DigitalOcean Marketplace making it a lot easier to build Node.js apps.","spans":[{"start":166,"end":215,"type":"hyperlink","data":{"link_type":"Web","url":"https://marketplace.digitalocean.com/apps/nodejs"}}]},{"type":"heading2","text":"Simple, predictable pricing","spans":[]},{"type":"paragraph","text":"Just like all DigitalOcean products, Managed MongoDB provides simple, predictable pricing that allows you to control costs and prevent any surprise bills. You can spin up a database cluster for just $15/month, or a highly available three-node replica set for $45/month. Click here for more information.","spans":[{"start":270,"end":301,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/pricing/#managed-databases"}}]},{"type":"heading2","text":"Regional availability","spans":[]},{"type":"paragraph","text":"Managed MongoDB is currently available in the following regions:","spans":[]},{"type":"list-item","text":"NYC3 (New York, USA)","spans":[]},{"type":"list-item","text":"FRA1 (Frankfurt, Germany)","spans":[]},{"type":"list-item","text":"AMS3 (Amsterdam, Netherlands)","spans":[]},{"type":"paragraph","text":"We will be making Managed Mongo available in other regions soon. Please check out the release notes for most up to date information on regional availability.","spans":[{"start":86,"end":99,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/docs/release-notes/"}}]},{"type":"heading2","text":"Join us at deploy, DigitalOcean’s virtual user conference","spans":[]},{"type":"paragraph","text":"Today we have deploy, DigitalOcean’s signature user conference, which focuses on celebrating, educating, and connecting awesome builders from all over the world.","spans":[{"start":14,"end":20,"type":"hyperlink","data":{"link_type":"Web","url":"https://deploy.digitalocean.com/home"}}]},{"type":"paragraph","text":"Check out the keynote session from DigitalOcean's CEO, Yancey Spruill, in which he talks about where we're headed as a company and shares some exciting product updates. His keynote will be followed by sessions from community members, engineers, customers, and other experts that are building technologies and businesses powered by the cloud. With live Q&A and an active Discord server, there’s ample opportunity to engage and learn something new. Click here to attend the deploy conference.","spans":[{"start":14,"end":69,"type":"hyperlink","data":{"link_type":"Web","url":"https://deploy.digitalocean.com/agenda/session/552806"}},{"start":347,"end":384,"type":"hyperlink","data":{"link_type":"Web","url":"http://do.co/deploy-discord"}},{"start":461,"end":489,"type":"hyperlink","data":{"link_type":"Web","url":"http://do.co/deploy"}}]},{"type":"paragraph","text":"We are also launching a hackathon for DigitalOcean Managed MongoDB. Learn how you can participate, submit an app and get a t-shirt.","spans":[{"start":24,"end":66,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/mongodb-hackathon"}}]},{"type":"paragraph","text":"We hope you will give Managed MongoDB a try. Here are some sample datasets and sample apps that you can use to kick the tires. Check out the docs and let us know what you think!","spans":[{"start":22,"end":43,"type":"hyperlink","data":{"link_type":"Web","url":"https://cloud.digitalocean.com/databases/new?engine=mongodb"}},{"start":59,"end":90,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/do-community/mongodb-resources","target":"_blank"}},{"start":141,"end":145,"type":"hyperlink","data":{"link_type":"Web","url":"https://docs.digitalocean.com/products/databases/mongodb/"}}]},{"type":"paragraph","text":"If you’d like to have a conversation about using DigitalOcean and Managed MongoDB in your business, please feel free to contact our sales team.","spans":[{"start":120,"end":142,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/company/contact/sales/"}}]},{"type":"paragraph","text":"Happy coding!","spans":[]},{"type":"paragraph","text":"André Bearfield","spans":[]},{"type":"paragraph","text":"Director of Product Management","spans":[]}],"tags":[{"tag1":{"__typename":"PRISMIC_Tag","tag":"Product Updates","_linkType":"Link.document","_meta":{"uid":"product-updates"}}}],"author":{"__typename":"PRISMIC_Author","author_name":"André Bearfield","author_image":{"dimensions":{"width":553,"height":547},"alt":"André Bearfield","copyright":null,"url":"https://images.prismic.io/www-static/fdc7c85186f0a850b04083e1d4306bd1c19772e8_andre-bearfield.png?auto=compress,format"},"_meta":{"uid":"andre-bearfield"}},"_meta":{"uid":"introducing-digitalocean-managed-mongodb"}},"featured_blog_2":{"__typename":"PRISMIC_Blog","_linkType":"Link.document","blog_header_image":{"dimensions":{"width":790,"height":400},"alt":"Droplet Console","copyright":null,"url":"https://images.prismic.io/www-static/710499ae-78cc-4179-afc1-15793637b200_DODX3727-790x400-logo-2.jpg?auto=compress,format"},"blog_headline":[{"type":"heading1","text":"Securely connect to Droplets with SSH key pairs using a new Droplet Console","spans":[]}],"blog_post_date":"2021-08-10","blog_post_content":[{"type":"paragraph","text":"The famous author Ken Blanchard once said, “Feedback is the breakfast of champions.\" This is something we truly believe at DigitalOcean, and we always strive to enhance our products based on customer feedback.","spans":[]},{"type":"paragraph","text":"With this goal in mind, we are excited to introduce a new Droplet Console that will make it much easier to connect to your Droplets securely. The new Droplet Console provides one-click SSH access to your Droplets through a native-like SSH/Terminal experience. It also eliminates the need for a password or manual configuration of SSH keys. Starting today, we’re pleased to announce that the new Droplet Console is now available to all Droplet users.","spans":[]},{"type":"heading2","text":"Why you should be using Secure Shell (SSH) ","spans":[]},{"type":"paragraph","text":"Password-based security is notoriously insecure due to password fatigue and the overuse of passwords such as ‘123456’. Secure Shell or SSH is a network communication protocol that solves this by using passwordless solutions for encryption, enabling two computers to communicate and securely share data. At a high level, SSH works by creating cryptographic key pairs consisting of a public and private key, which are computer generated and stored separately to ensure their security. ","spans":[{"start":80,"end":117,"type":"hyperlink","data":{"link_type":"Web","url":"https://cybernews.com/best-password-managers/most-common-passwords/"}}]},{"type":"paragraph","text":"SSH has become the default encryption protocol for many industries, but it was difficult to use SSH keys with DigitalOcean’s current Recovery (VNC) console, which is why we developed our new Droplet Console. The new Droplet Console is backed by an agent that security supervises the key pair, while also providing one-click SSH access to our users. You can see the full list of features below.","spans":[]},{"type":"heading2","text":"The new Droplet Console: More time saving, less time wasting ","spans":[]},{"type":"paragraph","text":"The new Droplet Console is for everyone who is looking to build fast, secure apps and avoid hassles with SSH access & usability issues.","spans":[]},{"type":"paragraph","text":"In addition to easier SSH access, the new Droplet Console comes with:","spans":[]},{"type":"list-item","text":"Copy/paste text: Instead of typing lengthy key pairs and text manually, you can use copy/paste to save time. ","spans":[{"start":0,"end":17,"type":"strong"}]},{"type":"list-item","text":"Multi-color support: Multi-color support makes the console more useful and intuitive, and breaks the conventional standard appearance which is black text on a white background. ","spans":[{"start":0,"end":41,"type":"strong"}]},{"type":"list-item","text":"Multi-language support: DigitalOcean’s new Droplet Console supports multiple languages, meaning you can now type and view any content in any language that is supported by UTF-8","spans":[{"start":0,"end":24,"type":"strong"}]},{"type":"list-item","text":"OS/images supported: Linux distributions (Ubuntu(16.04 - 20.04), Fedora (32 & 33), Debian (9), CentOS (7.6 & 8.3), CentOS 8 Stream, Rocky Linux and Marketplace images.","spans":[{"start":0,"end":20,"type":"strong"},{"start":148,"end":159,"type":"hyperlink","data":{"link_type":"Web","url":"https://marketplace.digitalocean.com/"}}]},{"type":"paragraph","text":"The new Droplet Console is available by default on any new Droplets you spin up. You can also enable it manually on older Droplets. Click here to learn more!","spans":[{"start":132,"end":157,"type":"hyperlink","data":{"link_type":"Web","url":"https://docs.digitalocean.com/products/droplets/how-to/connect-with-console/"}}]},{"type":"paragraph","text":"Check out this short walkthrough video that shows the new Droplet Console in action: ","spans":[]},{"type":"embed","oembed":{"type":"video","embed_url":"https://www.youtube.com/watch?v=Qt7QihVuxiE","title":"Access Your Droplet Terminal Through the Web Console","provider_name":"YouTube","thumbnail_url":"https://i.ytimg.com/vi/Qt7QihVuxiE/hqdefault.jpg","provider_url":"https://www.youtube.com/","author_name":"DigitalOcean","author_url":"https://www.youtube.com/c/Digitalocean","height":113,"width":200,"version":"1.0","thumbnail_height":360,"thumbnail_width":480,"html":"<iframe width=\"200\" height=\"113\" src=\"https://www.youtube.com/embed/Qt7QihVuxiE?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe>"}},{"type":"paragraph","text":"We hope you’re excited about the new Droplet Console. You’re welcome to spin some Droplets up right now, and try out the new Droplet Console – why wait?","spans":[{"start":72,"end":103,"type":"hyperlink","data":{"link_type":"Web","url":"https://cloud.digitalocean.com/droplets/new"}}]},{"type":"paragraph","text":"Happy coding!","spans":[]},{"type":"paragraph","text":"Harsh Banwait, Senior Product Manager","spans":[]}],"tags":[{"tag1":{"__typename":"PRISMIC_Tag","tag":"Product Updates","_linkType":"Link.document","_meta":{"uid":"product-updates"}}}],"author":{"__typename":"PRISMIC_Author","author_name":"Harsh Banwait","author_image":{"dimensions":{"width":600,"height":399},"alt":null,"copyright":null,"url":"https://images.prismic.io/www-static/e83ff690-b20c-4d88-a2b6-57e562558cd6_download.png?auto=compress,format"},"_meta":{"uid":"harsh-banwait"}},"_meta":{"uid":"new-droplet-console-ssh-support"}},"featured_blog_3":{"__typename":"PRISMIC_Blog","_linkType":"Link.document","blog_header_image":{"dimensions":{"width":790,"height":400},"alt":null,"copyright":null,"url":"https://images.prismic.io/www-static/588e28d3-d41e-480b-937b-8c3b19201f6e_DODX3568-790x400-Blog.jpg?auto=compress,format"},"blog_headline":[{"type":"heading1","text":"How to scale your SaaS product without breaking the bank","spans":[]}],"blog_post_date":"2021-06-22","blog_post_content":[{"type":"paragraph","text":"These days, if you are in the business of software, chances are you are delivering or plan to deliver your services using a Software-as-a-Service (SaaS) model. A combination of internet-based delivery, subscription-based pricing, and low-friction product experiences have made SaaS solutions valuable tools for their users, and an excellent vehicle for software builders looking to distribute their products.","spans":[]},{"type":"paragraph","text":"These factors have made SaaS solutions ubiquitous; SaaS is the largest segment in the public cloud market, and is used to provide functionality ranging from personal finance apps for consumers, to productivity software for businesses, and even tools and services for software developers themselves to compose their applications and simplify their workflows. It is also not uncommon to find micro-SaaS applications being built for specific industries such as retail, job functions such as accounting or marketing, or tasks such as event management. ","spans":[]},{"type":"paragraph","text":"The best thing about this SaaS wave has been that it has allowed a new generation of software builders to build and monetize applications and participate in the digital economy. Previously, you had to be a big company with lots of resources, name recognition and distribution networks to successfully sell software products. Now, irrespective of whether you are a single person working on a passion project, a small team of developers in a startup, or a small and medium-sized business (SMB), the SaaS model enables you to express your ideas in the form of software and deliver them to customers anywhere in the world.","spans":[]},{"type":"heading2","text":"The unique challenges of building SaaS solutions","spans":[]},{"type":"paragraph","text":"","spans":[]},{"type":"paragraph","text":"Despite the opportunities that come with the widespread adoption of SaaS products, software builders still have to answer key questions in their journey to building successful SaaS products. Understanding what customers to target, features to prioritize, how to price your product, and how to acquire customers are all critical questions to figure out while you are also doing the important job of actually building and operating the product. ","spans":[]},{"type":"paragraph","text":"Writing the code, testing, deployment, monitoring the usage in production, and ensuring that your apps are able to handle the additional demand when customer base and usage grows are all essential and time-consuming tasks.","spans":[]},{"type":"paragraph","text":"Additionally, being able to test multiple ideas, pivot, and double down on the ideas that actually work is critical in early stages of SaaS development. Once growth comes, it is equally important to scale up without compromising on performance or reliability. Needless to say, all of this needs to be economically viable as well, since not everyone has the resources of large SaaS providers like Salesforce or Adobe.","spans":[]},{"type":"heading2","text":"Cloud Computing enables builders but also poses challenges","spans":[]},{"type":"paragraph","text":"","spans":[]},{"type":"paragraph","text":"Fortunately, for the act of building and operating your apps, cloud computing can help take some load off your shoulders. Unless you have the scale and resources of Facebook, chances are you are not going to set up your own data centers to host the computing infrastructure that powers your SaaS company. Public cloud infrastructure providers can bring great value to SaaS builders by providing on-demand computing services with usage-based pricing. However, just like how the legacy software companies weren't built for the SaaS model, the early (and big) cloud computing services were not optimized for the unique needs of small SaaS building teams. ","spans":[]},{"type":"paragraph","text":"Smaller SaaS teams face challenges with large cloud computing providers, including:","spans":[]},{"type":"heading4","text":"Too many technology options","spans":[]},{"type":"paragraph","text":"There are just too many options for tech stacks on which to build your SaaS - programming languages, application development frameworks, libraries, runtime environments, architectural patterns, and deployment models - and the list is growing by the day.","spans":[]},{"type":"heading4","text":"Complexity of cloud computing services","spans":[]},{"type":"paragraph","text":"Even when you have decided on a technology stack, there is a lot of cloud vendor-specific terminology you need to learn and heavy lifting you need to do to build on the cloud, not all of which contributes to making your SaaS applications successful.","spans":[]},{"type":"heading4","text":"Unpredictable costs","spans":[]},{"type":"paragraph","text":"The experimentation necessary in early stages of SaaS development, as well as the scaling of applications required during the growth phase, call for affordable and predictable pricing from your cloud provider. The last thing SaaS teams want is surprising and indecipherable bills from your cloud provider. Unfortunately, smaller businesses often experience unpredictable costs with cloud providers who are busy serving only the large enterprises.","spans":[]},{"type":"heading2","text":"DigitalOcean provides a simple, cost effective solution for SaaS builders","spans":[]},{"type":"paragraph","text":"Fortunately, at DigitalOcean we have a laser focus on small software development teams, who are trying to build the next generation of applications. Today, DigitalOcean customers are already building SaaS applications which serve all kinds of customers.","spans":[{"start":191,"end":217,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/solutions/saas/"}}]},{"type":"paragraph","text":"We believe SaaS builders should focus on building apps that power their business, and not spend their valuable time on managing infrastructure. That is exactly what we have been able to enable through our intuitive products that are built for scale and reliability.","spans":[{"start":205,"end":223,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/products/"}}]},{"type":"list-item","text":"Vidazoo is an advertising technology company specializing in video streaming and serving. It serves video ads to thousands of websites and handles close to 10 billion requests per day. \n\n“We are as much a data company as an adtech company. Our business relies on speedy and accurate data processing at massive scale. DigitalOcean provides us the perfect set of tools to operate our SaaS business profitably, while not making us feel the need to become full time system administrators. We plan to move a lot of our apps to DigitalOcean App Platform and other fully managed products.” - Roman Svichar, CTO of Vidazoo","spans":[{"start":0,"end":7,"type":"hyperlink","data":{"link_type":"Web","url":"https://vidazoo.com/"}},{"start":187,"end":583,"type":"em"}]},{"type":"paragraph","text":"We believe in meeting customers where they are. If they already have an understanding of cloud infrastructure technologies, they should be able to leverage that knowledge and get started with our products without any further ramp up.","spans":[]},{"type":"list-item","text":"Whatfix is an enterprise SaaS provider that offers a digital adoption platform to businesses. The company helps enterprises gain the full value of their investments in enterprise applications by providing real-time, interactive, and contextual guidance to users of those applications. \n\n“What we really love about the DigitalOcean platform is the ease of use. We feel like we know infrastructure and can handle most of the configuration and management. What we needed from a cloud was not bells and whistles but efficiency and reliability. DigitalOcean provides us a platform to build our apps and then gets out of the way. Just how we like it.” - Achyuth Krishna, Director of Engineering of Whatfix","spans":[{"start":0,"end":7,"type":"hyperlink","data":{"link_type":"Web","url":"https://whatfix.com/blog/driving-the-future-now-were-excited-to-announce-our-90-million-series-d-funding/"}},{"start":287,"end":648,"type":"em"}]},{"type":"paragraph","text":"We understand that scaling while maintaining reliability of applications and profitability of business is important, so we provide robust solutions which minimize downtime.","spans":[]},{"type":"list-item","text":"Centra is a SaaS-based e-commerce platform for global direct-to-consumer and wholesale e-commerce brands. Centra provides a powerful e-commerce backend that lets brands build pixel-perfect, custom designed, online flagship stores. \n\n“How do we enable our customers to create differentiated online experiences? How do we ensure their e-commerce apps stay up and running at all times? How do we scale on-demand when traffic grows or new customers come in? These are the questions that we ask ourselves every day. Thankfully, we have a partner in DigitalOcean that provides just the platform to answer those questions enabling us to guarantee 99.9% uptime for our clients.” - Martin Jensen, CEO of Centra","spans":[{"start":0,"end":6,"type":"hyperlink","data":{"link_type":"Web","url":"https://centra.com/"}},{"start":233,"end":673,"type":"em"}]},{"type":"paragraph","text":"These are just a few examples of SaaS businesses finding success on DigitalOcean. We are constantly amazed by the creativity and innovation that software builders are utilizing our platform for. If you are interested in learning more about product updates, technical deep-dives and best practices for building SaaS products and businesses, please contact us to learn how we can help you get started. ","spans":[{"start":340,"end":357,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/migrate/?utmmedium=blog","target":"_blank"}}]},{"type":"paragraph","text":"Come build with DigitalOcean!","spans":[]},{"type":"paragraph","text":"Looking to migrate your SaaS to DigitalOcean? Leverage free infrastructure credits, robust training, and technical support to ensure a worry-free migration.","spans":[{"start":0,"end":156,"type":"strong"},{"start":0,"end":156,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/migrate/?utmmedium=blog","target":"_blank"}}]},{"type":"paragraph","text":"","spans":[]},{"type":"paragraph","text":"Raman Sharma","spans":[]},{"type":"paragraph","text":"Vice President, Product & Programs Marketing","spans":[]}],"tags":[{"tag1":{"__typename":"PRISMIC_Tag","tag":"Developer Relations","_linkType":"Link.document","_meta":{"uid":"developer-relations"}}}],"author":{"__typename":"PRISMIC_Author","author_name":"Raman Sharma","author_image":{"dimensions":{"width":512,"height":512},"alt":null,"copyright":null,"url":"https://images.prismic.io/www-static/497b4b14-d192-493a-8b66-7ae176ba99f3_raman.png?auto=compress,format"},"_meta":{"uid":"raman-sharma"}},"_meta":{"uid":"how-to-scale-your-saas-product-without-breaking-the-bank"}}}}]}}},"pageContext":{"limit":12,"skip":24,"numTagPages":10,"currentPage":3,"uid":"community","data":[{"node":{"author":{"_linkType":"Link.document","author_name":"Community Team","author_image":null,"_meta":{"uid":"community_team"}},"blog_header_image":{"dimensions":{"width":1024,"height":512},"alt":"Machine Learning book illustration","copyright":null,"url":"https://images.prismic.io/www-static/f19477fbeea318fdb15d057e9ccc8ee570ae2da3_machine-learning-book.png?auto=compress,format"},"blog_headline":[{"type":"heading1","text":"Celebrate PyCon 2019 With Our Free Python Machine Learning Projects eBook","spans":[]}],"blog_post_content":[{"type":"paragraph","text":"To commemorate the 2019 PyCon conference and the worldwide Python community, we have put together a free eBook of Python Machine Learning Projects!","spans":[{"start":114,"end":146,"type":"hyperlink","data":{"link_type":"Web","url":"https://do.co/py-ml-book"}}]},{"type":"paragraph","text":"Project-based learning offers the opportunity to gain hands-on experience by digging into complex, real-world challenges. You can download this book and read it offline, allowing you to work at your own pace as you go through machine learning Python projects. If you are a teacher or workshop leader, you may also use this resource with students or community members.","spans":[]},{"type":"paragraph","text":"The book is Creative Commons licensed, so feel free to redistribute and remix the tutorials (with attribution) for your noncommercial educational needs!","spans":[{"start":12,"end":37,"type":"hyperlink","data":{"link_type":"Web","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"}}]},{"type":"paragraph","text":"You can download the book in the following formats:","spans":[]},{"type":"list-item","text":"ePub","spans":[{"start":0,"end":4,"type":"hyperlink","data":{"link_type":"Web","url":"https://do.co/py-ml-book-epub"}}]},{"type":"list-item","text":"PDF","spans":[{"start":0,"end":3,"type":"hyperlink","data":{"link_type":"Web","url":"https://do.co/py-ml-book-pdf"}}]},{"type":"list-item","text":"Mobi (compatible with Kindle).","spans":[{"start":0,"end":4,"type":"hyperlink","data":{"link_type":"Web","url":"https://do.co/py-ml-book-mobi"}}]},{"type":"heading2","text":"Why Machine Learning?","spans":[]},{"type":"paragraph","text":"Machine learning is increasingly being used to find patterns, conduct analysis, and make decisions – sometimes without final input from humans who may be impacted by these findings. We created this book to equip developers with tools they can use to better understand, evaluate, and shape machine learning, in order to help ensure that it serves everyone fairly.","spans":[]},{"type":"paragraph","text":"This book will set you up with a Python programming environment if you don’t have one already, then provide you with a conceptual understanding of machine learning. It includes three Python machine learning tutorials that will help you create a machine learning classifier, build a neural network to recognize handwritten digits, and give you a background in deep reinforcement learning through building a bot for Atari.","spans":[]},{"type":"paragraph","text":"If you need Python support or would like reference material, check out our free How To Code in Python 3 eBook!","spans":[{"start":80,"end":103,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/digitalocean-ebook-how-to-code-in-python"}}]},{"type":"heading2","text":"By the Community for the Community 🐍","spans":[]},{"type":"paragraph","text":"These chapters originally appeared as articles on DigitalOcean's Community site, written and edited by members of the international software developer community. If you are interested in contributing to this knowledge base, consider participating in our Write for DOnations program. DigitalOcean offers payment to authors and provides a matching donation to tech-focused nonprofits.","spans":[{"start":50,"end":79,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community"}},{"start":254,"end":281,"type":"hyperlink","data":{"link_type":"Web","url":"https://do.co/w4do"}}]},{"type":"paragraph","text":"This eBook was put together by members of the DigitalOcean Developer Education team. To learn more about our eBook creation process,  read the blog post we wrote announcing our How To Code in Python 3 eBook.","spans":[{"start":143,"end":206,"type":"hyperlink","data":{"link_type":"Web","url":"https://blog.digitalocean.com/how-to-code-in-python-ebook/"}}]},{"type":"heading2","text":"Find Us at PyCon","spans":[]},{"type":"paragraph","text":"This year we are happy to be sponsoring PyCon 2019 Sprints, which offer developers the opportunity to collaborate in person on open source projects. Members of the DigitalOcean Community team will be at the conference, so if you are in Cleveland come find us for some great Sammy swag! We also proudly support the Python Software Foundation as a Bronze Sponsor.","spans":[{"start":40,"end":58,"type":"hyperlink","data":{"link_type":"Web","url":"https://us.pycon.org/2019/community/sprints/"}},{"start":314,"end":340,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.python.org/psf/"}}]}],"blog_post_date":"2019-05-03","tags":[{"tag1":{"tag":"Community","_linkType":"Link.document","_meta":{"uid":"community"}}},{"tag1":{"tag":"Developer Relations","_linkType":"Link.document","_meta":{"uid":"developer-relations"}}},{"tag1":{"tag":"Engineering","_linkType":"Link.document","_meta":{"uid":"engineering"}}}],"_meta":{"uid":"gear-up-for-pycon-2019-with-digitaloceans-free-python-machine-learning-projects-ebook"}}},{"node":{"author":{"_linkType":"Link.document","author_name":"Kamal Nasser","author_image":{"dimensions":{"width":1008,"height":1008},"alt":null,"copyright":null,"url":"https://images.prismic.io/www-static/e2285fcfaf32ce7ec26329fe7e416ae896fbf991_portrait_2k18_bw_smallres.jpg?auto=compress,format"},"_meta":{"uid":"kamal-nasser"}},"blog_header_image":{"dimensions":{"width":1200,"height":900},"alt":null,"copyright":null,"url":"https://images.prismic.io/www-static/c4446d83-1e27-4c4e-822c-5ca308967e52_database-mostov_dribbble.png?auto=compress,format"},"blog_headline":[{"type":"heading1","text":"Creating a Simple Contacts List with Go and PostgreSQL","spans":[]}],"blog_post_content":[{"type":"paragraph","text":"In this post, we will build a simple web page containing a contacts list, with the contacts fetched from a PostgreSQL database. We will connect to the database in Go and use PostgreSQL's support for JSON columns. This is what the result will look like:","spans":[]},{"type":"image","url":"https://images.prismic.io/www-static/76f9193e-d599-4c79-b1d0-015d71bce6fb_Kamal-1.png?auto=compress,format","alt":null,"copyright":null,"dimensions":{"width":1600,"height":1396}},{"type":"paragraph","text":"By following this post, you will learn how to connect to a PostgreSQL database in Go using the sqlx and pgx packages, render data dynamically using a template, and serve the resulting page on an HTTP server.","spans":[]},{"type":"heading1","text":"Requirements","spans":[]},{"type":"paragraph","text":"Before we get started:","spans":[]},{"type":"o-list-item","text":"Make sure you have Go installed. See this post for instructions.  ","spans":[{"start":37,"end":46,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.callicoder.com/golang-installation-setup-gopath-workspace/"}}]},{"type":"o-list-item","text":"Make sure you know where your GOPATH is. It's usually ~/go unless set differently.","spans":[]},{"type":"heading1","text":"Getting an HTTP Server Up","spans":[]},{"type":"paragraph","text":"In a new empty directory inside your $GOPATH, create a file named main.go. You can name the directory anything you like: I went with go-contacts. We'll start with setting up the HTTP server using Go's built-in net/http package.","spans":[]},{"type":"preformatted","text":"    package main\n    import (\n        \"flag\"\n        \"log\"\n        \"net/http\"\n        \"os\"\n    )\n    var (\n        listenAddr = flag.String(\"addr\", getenvWithDefault(\"LISTENADDR\", \":8080\"), \"HTTP address to listen on\")\n    )\n    func getenvWithDefault(name, defaultValue string) string {\n            val := os.Getenv(name)\n            if val == \"\" {\n                    val = defaultValue\n            }\n            return val\n    }\n    func main() {\n        flag.Parse()\n        log.Printf(\"listening on %s\\n\", *listenAddr)\n        http.ListenAndServe(*listenAddr, nil)\n    }","spans":[]},{"type":"paragraph","text":"The server will want a host and a port to listen on, so we ask for that in a CLI flag named addr. We also want to offer the option to pass in the setting in an environment variable, so the default value for the flag will be taken from the LISTENADDR environment variable. This means that if the CLI flag is passed, the value of the environment variable will be used. If neither are set, we'll fall back to port 8080.","spans":[]},{"type":"paragraph","text":"If you save the file and run it now, you should be able to browse to http://localhost:8080.","spans":[{"start":69,"end":90,"type":"hyperlink","data":{"link_type":"Web","url":"http://localhost:8080"}}]},{"type":"preformatted","text":"go run main.go","spans":[]},{"type":"paragraph","text":"and see—hold on, is that a \"404 page not found\" error?!","spans":[]},{"type":"paragraph","text":"That's fine! It's because we haven't configured any routes or pages yet, so the server doesn't know how to respond to the request. Why don't we go ahead and do that now.","spans":[]},{"type":"heading2","text":"Contacts List Page","spans":[]},{"type":"paragraph","text":"Let's create the contacts list page and serve it on the root path, /. We'll use the template/html package so that we can easily pass in dynamic data (the contacts) to be rendered in the page later.","spans":[]},{"type":"paragraph","text":"Create a directory named templates alongside main.go and within it a file named index.html with the following content:","spans":[]},{"type":"preformatted","text":"    <!doctype html>\n    <html>\n        <head>\n            <meta charset=\"utf-8\">\n            <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n            <title>Contacts</title>\n            <link rel=\"stylesheet\" href=\"https://unpkg.com/tachyons@4.10.0/css/tachyons.min.css\"/>\n        </head>\n        <body>\n            <div class=\"mw6 center pa3 sans-serif\">\n                <h1 class=\"mb4\">Contacts</h1>\n            </div>\n        </body>\n    </html>","spans":[]},{"type":"paragraph","text":"This is a page with basic styling that will serve as the base for our contacts list.","spans":[]},{"type":"paragraph","text":"Now we need to read the index.html template in our program. Import html/template and add a global variable to hold the templates right after listenAddr at the top:","spans":[]},{"type":"preformatted","text":"    import (\n        \"flag\"\n        \"log\"\n        \"html/template\"\n        \"net/http\"\n    )\n    var (\n            listenAddr       = flag.String(\"addr\", getenvWithDefault(\"LISTENADDR\", \":8080\"), \"HTTP address to listen on\")\n            tmpl             = template.New(\"\")\n    )","spans":[]},{"type":"paragraph","text":"Inside main(), after the flag.Parse() line, add the following. For compatibility with all operating systems, import the path/filepath package as we will use to construct the path to the template files.","spans":[]},{"type":"preformatted","text":"    var err error\n    _, err = tmpl.ParseGlob(filepath.Join(\".\", \"templates\", \"*.html\"))\n    if err != nil {\n        log.Fatalf(\"Unable to parse templates: %v\\n\", err)\n    }","spans":[]},{"type":"paragraph","text":"This will read every HTML file in the templates directory and prepare it for rendering. Now that we've done that, we want to configure the template to be rendered on /. Add a new function at the very bottom of the file to serve the page:","spans":[]},{"type":"preformatted","text":"    func handler(w http.ResponseWriter, r *http.Request) {\n        tmpl.ExecuteTemplate(w, \"index.html\", nil)\n    }","spans":[]},{"type":"paragraph","text":"Finally, configure the server to use this handler function. Above the log.Printf() line in main(), add:","spans":[]},{"type":"preformatted","text":"http.HandleFunc(\"/\", handler)","spans":[]},{"type":"paragraph","text":"Now we're ready! The whole file should look like this:","spans":[]},{"type":"preformatted","text":"    package main\n\n    import (\n        \"flag\"\n        \"log\"\n        \"html/template\"\n        \"net/http\"\n    )\n    var (\n        listenAddr = flag.String(\"addr\", getenvWithDefault(\"LISTENADDR\", \":8080\"), \"HTTP address to listen on\")\n        tmpl       = template.New(\"\")\n    )\n    \n    func getenvWithDefault(name, defaultValue string) string {\n            val := os.Getenv(name)\n            if val == \"\" {\n                    val = defaultValue\n            }\n            return val\n    }\n\n    func main() {\n        flag.Parse()\n        var err error\n        _, err = tmpl.ParseGlob(filepath.Join(\".\", \"templates\", \"*.html\"))\n        if err != nil {\n            log.Fatalf(\"Unable to parse templates: %v\\n\", err)\n        }\n        http.HandleFunc(\"/\", handler)\n        log.Printf(\"listening on %s\\n\", *listenAddr)\n        http.ListenAndServe(*listenAddr, nil)\n    }\n    func handler(w http.ResponseWriter, r *http.Request) {\n        tmpl.ExecuteTemplate(w, \"index.html\", nil)\n    }","spans":[]},{"type":"paragraph","text":"Run go run main.go again and you should see the template we've configured.","spans":[]},{"type":"image","url":"https://images.prismic.io/www-static/4d14d033-b87d-4e16-ad5c-84cad8b14f7a_Kamal-2.png?auto=compress,format","alt":null,"copyright":null,"dimensions":{"width":1600,"height":994}},{"type":"heading1","text":"Contacts in a Database","spans":[]},{"type":"paragraph","text":"Something is missing in the page—the actual contacts! Let's add them in.","spans":[]},{"type":"paragraph","text":"We will use DigitalOcean Databases to quickly get a PostgreSQL cluster up. If you haven’t yet, create a new one—it only takes a few minutes: if you prefer a text post, see the product documentation for Databases. If you prefer a video, click here.","spans":[{"start":172,"end":211,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/docs/databases/how-to/clusters/create/"}},{"start":236,"end":246,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.youtube.com/watch?v=jY5FhyiEdig"}}]},{"type":"image","url":"https://images.prismic.io/www-static/a010b376-74b2-4837-892f-db1d7d8da99e_Kamal-3.png?auto=compress,format","alt":null,"copyright":null,"dimensions":{"width":1600,"height":427}},{"type":"paragraph","text":"Once you've created the cluster, copy its Connection String from the control panel. In the Connection Details section in the Overview page, choose \"Connection string\" from the list and copy it:","spans":[]},{"type":"image","url":"https://images.prismic.io/www-static/2c01727f-4ff8-4f5c-ada2-80ff7f171168_Kamal-4.png?auto=compress,format","alt":null,"copyright":null,"dimensions":{"width":990,"height":540}},{"type":"paragraph","text":"The connection string contains all the details necessary to connect to your database (including your password) so be sure to keep it safe.","spans":[]},{"type":"heading2","text":"Initializing the Database","spans":[]},{"type":"paragraph","text":"Our Go app will only handle displaying the contacts, so I have prepared an SQL export containing 10 randomly generated contacts that you can import into your database. You can find it here.","spans":[{"start":184,"end":188,"type":"hyperlink","data":{"link_type":"Web","url":"https://raw.githubusercontent.com/digitalocean/databases/master/examples/contacts.sql"}}]},{"type":"paragraph","text":"On macOS, I like to use TablePlus to work with my databases, but you can use any client you prefer or import it using the ```[php]{`psql`}``` CLI command like so:","spans":[]},{"type":"preformatted","text":"psql 'your connection string here' < contacts.sql","spans":[]},{"type":"heading2","text":"Fetching the Contacts","spans":[]},{"type":"paragraph","text":"Ok, so now we have a database with some contacts in it 🎉 Let's have our program connect to it and fetch the contacts. We'll build this functionality step by step.","spans":[]},{"type":"paragraph","text":"There are many ways to connect to a PostgreSQL database in Go. In this case, we also need a convenient way to access JSONB fields since our contacts database uses them. I personally found the combination of `github.com/jmoiron/sqlx` and `github.com/jackc/pgx` to work best.","spans":[{"start":207,"end":232,"type":"hyperlink","data":{"link_type":"Web","url":"http://github.com/jmoiron/sqlx"}},{"start":237,"end":259,"type":"hyperlink","data":{"link_type":"Web","url":"http://github.com/jackc/pgx"}}]},{"type":"paragraph","text":"Start by importing the packages:","spans":[]},{"type":"preformatted","text":"go get -u -v github.com/jackc/pgx github.com/jmoiron/sqlx","spans":[]},{"type":"paragraph","text":"And adding them at the top of main.go:","spans":[]},{"type":"preformatted","text":"    import (\n        ...\n\n        _ \"github.com/jackc/pgx/stdlib\"\n        \"github.com/jmoiron/sqlx\"\n        \"github.com/jmoiron/sqlx/types\"\n    )","spans":[]},{"type":"paragraph","text":"Now, there are a few things that we need to do. We need to define the Contact type based on the database's table structure and connect to our PostgreSQL database. When serving the contacts page, we will query the database for the contacts and pass them to the template for rendering.","spans":[]},{"type":"heading3","text":"Contact Type","spans":[]},{"type":"paragraph","text":"Add these types to main.go. They match the structure of the contacts database export and prepare support for the JSONB field favorites:  ","spans":[{"start":56,"end":84,"type":"hyperlink","data":{"link_type":"Web","url":"https://raw.githubusercontent.com/digitalocean/databases/master/examples/contacts.sql"}}]},{"type":"preformatted","text":"    // ContactFavorites is a field that contains a contact's favorites\n    type ContactFavorites struct {  \n        Colors []string `json:\"colors\"`\n    }\n    // Contact represents a Contact model in the database \n    type Contact struct {  \n        ID                   int\n        Name, Address, Phone string\n        FavoritesJSON types.JSONText    `db:\"favorites\"`\n        Favorites     *ContactFavorites `db:\"-\"`\n        CreatedAt string `db:\"created_at\"`\n        UpdatedAt string `db:\"updated_at\"`\n    }","spans":[]},{"type":"heading3","text":"Database Connection","spans":[]},{"type":"paragraph","text":"Note that we haven't connected to the database yet 👀 Let's do that now. We'll pass in the PostgreSQL connection string as a CLI flag and add a global database variable. So again at the top of main.go:","spans":[]},{"type":"preformatted","text":"    var (\n        connectionString = flag.String(\"conn\", getenvWithDefault(\"DATABASE_URL\", \"\"), \"PostgreSQL connection string\")\n        listenAddr       = flag.String(\"addr\", \":8080\", \"HTTP address to listen on\")\n        db               *sqlx.DB\n        tmpl             = template.New(\"\")\n    )","spans":[]},{"type":"paragraph","text":"Note that we use the function getenvWithDefault like with the listen address to allow the connection string to be passed using an environment variable (DATABASE_URL) in addition to the CLI flag (-conn).","spans":[]},{"type":"paragraph","text":"After the templating logic in main()(right above http.HandleFunc()), add the following:","spans":[]},{"type":"preformatted","text":"    if *connectionString == \"\" {\n        log.Fatalln(\"Please pass the connection string using the -conn option\")\n    }\n    \n    db, err = sqlx.Connect(\"pgx\", *connectionString)\n    if err != nil {\n        log.Fatalf(\"Unable to establish connection: %v\\n\", err)\n    }","spans":[]},{"type":"paragraph","text":"We're now connected to our PostgreSQL database!","spans":[]},{"type":"heading3","text":"Querying the Database for Contacts","spans":[]},{"type":"paragraph","text":"Add a new function to the bottom of the file to fetch all contacts from the database. For clearer errors, we'll make use of another package: github.com/pkg/errors. Download it and import it at the top of main.go as usual.","spans":[]},{"type":"preformatted","text":"    go get -u -v github.com/pkg/errors\n    \n    import (\n        ...\n        \"github.com/pkg/errors\"\n        ...\n    )\n    \n    …\n    \n    func fetchContacts() ([]*Contact, error) {\n        contacts := []*Contact{}\n        err := db.Select(&contacts, \"select * from contacts\")\n        if err != nil {\n            return nil, errors.Wrap(err, \"Unable to fetch contacts\")\n        }\n    \n        return contacts, nil\n    }","spans":[]},{"type":"paragraph","text":"One thing that's missing right now is the favorites column. If you look at the Contact type, we've defined this field: FavoritesJSON types.JSONText db:\"favorites\". This maps the favorites column in the database to the FavoritesJSON field in the Contact struct, making it available as a JSON object serialized as text.","spans":[]},{"type":"paragraph","text":"This means that we need to manually parse and un-marshal the JSON objects into actual Go structs. We will use Go’s encoding/json package so make sure to import it at the top of main.go. Adding onto fetchContacts():","spans":[]},{"type":"preformatted","text":"    import (\n        ...\n        \"encoding/json\"\n        ...\n    )\n    ...\n    func fetchContacts() ([]*Contact, error) {\n        ...\n    \n        for _, contact := range contacts {\n            err := json.Unmarshal(contact.FavoritesJSON, &contact.Favorites)\n    \n            if err != nil {\n                return nil, errors.Wrap(err, \"Unable to parse JSON favorites\")\n            }\n        }\n    \n        return contacts, nil\n    }","spans":[]},{"type":"paragraph","text":"The resulting structs will be stored in the Favorites field in the Contact struct.","spans":[]},{"type":"heading2","text":"Rendering the Contacts","spans":[]},{"type":"paragraph","text":"Cool, we have data. Let's use it! Inside the handler() function, we'll use fetchContacts() to get the contacts and then pass them to the template:","spans":[]},{"type":"preformatted","text":"    func handler(w http.ResponseWriter, r *http.Request) {\n        contacts, err := fetchContacts()\n        if err != nil {\n            w.WriteHeader(http.StatusInternalServerError)\n            w.Write([]byte(err.Error()))\n            return\n        }\n    \n        tmpl.ExecuteTemplate(w, \"index.html\", struct{ Contacts []*Contact }{contacts})\n    }","spans":[]},{"type":"paragraph","text":"This will attempt to fetch the contacts, display an error on failure, and pass them to the template. Note that if an error occurs, the full error will be sent as the response. In a production environment you will want to log the error and send a generic error message instead.","spans":[]},{"type":"paragraph","text":"Now we need to modify the template to do something with the contacts we are passing to it. To display favorite colors as a comma-separated list, we'll use the strings.Join function. Before we are able to use it inside the template, we need to define it as a template function, inside main() above the tmpl.ParseGlob line. Don’t forget to import the strings package at the top:","spans":[]},{"type":"preformatted","text":"    import (\n        ...\n        \"strings\"\n        ...\n    )\n    \n    …\n    \n    tmpl.Funcs(template.FuncMap{\"StringsJoin\": strings.Join})\n    _, err = tmpl.ParseGlob(filepath.Join(\".\", \"templates\", \"*.html\"))\n    \n    ...","spans":[]},{"type":"paragraph","text":"Then, under the <h1> line in the HTML template, add the following:","spans":[]},{"type":"preformatted","text":"    {{range .Contacts}}\n    <div class=\"pa2 mb3 striped--near-white\">\n        <header class=\"b mb2\">{{.Name}}</header>\n        <div class=\"pl2\">\n            <p class=\"mb2\">{{.Phone }}</p>\n            <p class=\"pre mb3\">{{.Address}}</p>\n            <p class=\"mb2\"><span class=\"fw5\">Favorite colors:</span> {{StringsJoin .Favorites.Colors \", \"}}</p>\n        </div>\n    </div>\n    {{end}}","spans":[]},{"type":"paragraph","text":"That's all! The final main.go file should look like so:","spans":[]},{"type":"preformatted","text":"    package main\n    \n    import (\n        \"encoding/json\"\n        \"flag\"\n        \"log\"\n        \"html/template\"\n        \"net/http\"\n        \"path/filepath\"\n        \"strings\"\n    \n        _ \"github.com/jackc/pgx/stdlib\"\n        \"github.com/jmoiron/sqlx\"\n        \"github.com/jmoiron/sqlx/types\"\n        \"github.com/pkg/errors\"\n    )\n    \n    // ContactFavorites is a field that contains a contact's favorites\n    type ContactFavorites struct {\n        Colors []string `json:\"colors\"`\n    }\n    \n    // Contact represents a Contact model in the database    \n    type Contact struct {\n        ID                   int\n        Name, Address, Phone string\n    \n        FavoritesJSON types.JSONText    `db:\"favorites\"`\n        Favorites     *ContactFavorites `db:\"-\"`\n    \n        CreatedAt string `db:\"created_at\"`\n        UpdatedAt string `db:\"updated_at\"`\n    }\n    \n    var (\n        connectionString = flag.String(\"conn\", getenvWithDefault(\"DATABASE_URL\", \"\"), \"PostgreSQL connection string\")\n        listenAddr       = flag.String(\"addr\", getenvWithDefault(\"LISTENADDR\", \":8080\"), \"HTTP address to listen on\")\n        db               *sqlx.DB\n        tmpl             = template.New(\"\")\n    )\n    \n    func getenvWithDefault(name, defaultValue string) string {\n            val := os.Getenv(name)\n            if val == \"\" {\n                    val = defaultValue\n            }\n    \n            return val\n    }\n    \n    func main() {\n        flag.Parse()\n        var err error\n    \n        // templating\n    \n        tmpl.Funcs(template.FuncMap{\"StringsJoin\": strings.Join})\n        _, err = tmpl.ParseGlob(filepath.Join(\".\", \"templates\", \"*.html\"))\n        if err != nil {\n            log.Fatalf(\"Unable to parse templates: %v\\n\", err)\n        }\n    \n        // postgres connection\n    \n        if *connectionString == \"\" {\n            log.Fatalln(\"Please pass the connection string using the -conn option\")\n        }\n    \n        db, err = sqlx.Connect(\"pgx\", *connectionString)\n        if err != nil {\n            log.Fatalf(\"Unable to establish connection: %v\\n\", err)\n        }\n    \n        // http server\n    \n        http.HandleFunc(\"/\", handler)\n    \n        log.Printf(\"listening on %s\\n\", *listenAddr)\n        http.ListenAndServe(*listenAddr, nil)\n    }\n    \n    func fetchContacts() ([]*Contact, error) {\n        contacts := []*Contact{}\n        err := db.Select(&contacts, \"select * from contacts\")\n        if err != nil {\n            return nil, errors.Wrap(err, \"Unable to fetch contacts\")\n        }\n    \n        for _, contact := range contacts {\n            err := json.Unmarshal(contact.FavoritesJSON, &contact.Favorites)\n    \n            if err != nil {\n                return nil, errors.Wrap(err, \"Unable to parse JSON favorites\")\n            }\n        }\n    \n        return contacts, nil\n    }\n    \n    func handler(w http.ResponseWriter, r *http.Request) {\n        contacts, err := fetchContacts()\n        if err != nil {\n            w.WriteHeader(http.StatusInternalServerError)\n            w.Write([]byte(err.Error()))\n            return\n        }\n    \n        tmpl.ExecuteTemplate(w, \"index.html\", struct{ Contacts []*Contact }{contacts})\n    }","spans":[]},{"type":"paragraph","text":"Run the program again, passing in your database's connection string like so and you should see the contacts list:","spans":[]},{"type":"preformatted","text":"    go run main.go -conn \"connection string here\"\n    # alternatively:\n    DATABASE_URL=\"connection string here\" go run main.go","spans":[]},{"type":"image","url":"https://images.prismic.io/www-static/3c4e728b-5295-49b9-af19-4ea7e7b4a730_Kamal-5.png?auto=compress,format","alt":null,"copyright":null,"dimensions":{"width":1600,"height":1396}},{"type":"heading1","text":"Conclusion","spans":[]},{"type":"paragraph","text":"After following this post, you will have learned how to build a simple contacts list step-by-step, starting with an empty page served by an HTTP web-server and ending with one that renders a list of contacts fetched from a PostgreSQL database. Along the way, you will have become familiar with using html/template to render a web page with dynamic data, connecting to a PostgreSQL database, and interacting with JSONB objects stored in the database.","spans":[]},{"type":"heading2","text":"Next Steps","spans":[]},{"type":"paragraph","text":"Here are some things you can do after following this post for further practice:","spans":[]},{"type":"list-item","text":"Print favorite colors as a bullet point list with each color being a separate item. Use html/template's built-in range function to loop over the favorite colors slice.","spans":[]},{"type":"list-item","text":"Add a favorite shape (square, circle, etc.) to one or more contacts and edit the template to display it. The Contact struct should stay unmodified.","spans":[]},{"type":"list-item","text":"List the contacts in the order that they were last updated, most recent first.","spans":[]},{"type":"paragraph","text":"[Hungry for more tutorials? Try Kamal's guides to \"Creating a Simple Contacts List with Laravel and PostgreSQL\" and \"Deploying a Fully-automated Git-based Static Website in Under 5 Minutes\"]","spans":[{"start":50,"end":111,"type":"hyperlink","data":{"link_type":"Web","url":"https://blog.digitalocean.com/create-simple-contacts-laravel-postgresql/"}},{"start":116,"end":189,"type":"hyperlink","data":{"link_type":"Web","url":"https://blog.digitalocean.com/deploying-a-fully-automated-git-based-static-website-in-under-5-minutes/"}}]},{"type":"paragraph","text":"Kamal Nasser is a Developer Advocate at DigitalOcean. He is also a Computer Science student with a passion for software engineering and avocados. You can find him on Twitter @kamaln7.","spans":[{"start":0,"end":183,"type":"em"},{"start":174,"end":182,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/kamaln7"}}]}],"blog_post_date":"2019-03-18","tags":[{"tag1":{"tag":"Developer Relations","_linkType":"Link.document","_meta":{"uid":"developer-relations"}}},{"tag1":{"tag":"Community","_linkType":"Link.document","_meta":{"uid":"community"}}}],"_meta":{"uid":"create-a-simple-contacts-list-with-go"}}},{"node":{"author":{"_linkType":"Link.document","author_name":"Kamal Nasser","author_image":{"dimensions":{"width":1008,"height":1008},"alt":null,"copyright":null,"url":"https://images.prismic.io/www-static/e2285fcfaf32ce7ec26329fe7e416ae896fbf991_portrait_2k18_bw_smallres.jpg?auto=compress,format"},"_meta":{"uid":"kamal-nasser"}},"blog_header_image":{"dimensions":{"width":1200,"height":900},"alt":null,"copyright":null,"url":"https://images.prismic.io/www-static/cfc9e5e6-a7f9-4565-8023-a8a94fd572fb_database-mostov_dribbble.png?auto=compress,format"},"blog_headline":[{"type":"heading1","text":"Creating a Simple Contacts List with Laravel and PostgreSQL","spans":[]}],"blog_post_content":[{"type":"paragraph","text":"In this post, we will build a simple Laravel app that displays a contacts list on a page. Using Eloquent and PostgreSQL's JSON object support, the app will query the database for the contacts and their details. This is what the result will look like:","spans":[]},{"type":"image","url":"https://images.prismic.io/www-static/03a96195-00d0-4aa8-8c94-d05375d015f6_Kamal-1.png?auto=compress,format","alt":null,"copyright":null,"dimensions":{"width":1600,"height":1396}},{"type":"paragraph","text":"By doing this, you will learn how to connect Laravel to a DigitalOcean Managed PostgreSQL database cluster, initialize the database with random data using Laravel factories and seeders, and store and read JSON documents in PostgreSQL using Laravel Eloquent.","spans":[{"start":58,"end":106,"type":"hyperlink","data":{"link_type":"Web","url":"https://blog.digitalocean.com/announcing-managed-databases-for-postgresql/"}}]},{"type":"heading2","text":"Requirements","spans":[]},{"type":"paragraph","text":"This post assumes that you have a working PHP development environment. You will need:","spans":[]},{"type":"list-item","text":"PHP 7.1+","spans":[]},{"type":"list-item","text":"Composer","spans":[{"start":0,"end":8,"type":"hyperlink","data":{"link_type":"Web","url":"https://getcomposer.org/doc/00-intro.md#installation-linux-unix-macos"}}]},{"type":"list-item","text":"Laravel’s required PHP extensions","spans":[{"start":10,"end":33,"type":"hyperlink","data":{"link_type":"Web","url":"https://laravel.com/docs/5.7/installation#server-requirements"}}]},{"type":"heading2","text":"Step 1: Create a Base Laravel App","spans":[]},{"type":"paragraph","text":"Let's start by creating a new blank Laravel app that will serve as a base for our web app. In a directory of your liking, generate a new project using Composer:","spans":[]},{"type":"preformatted","text":"```[php]{`","spans":[]},{"type":"paragraph","text":"    composer create-project --prefer-dist laravel/laravel laravel-contacts","spans":[]},{"type":"preformatted","text":"`}```","spans":[]},{"type":"paragraph","text":"If you browse into the newly-created `laravel-contacts` directory and run Laravel's built-in web server, you will see the default Laravel welcome page:","spans":[]},{"type":"preformatted","text":"```[php]{`","spans":[]},{"type":"paragraph","text":"    cd laravel-contacts","spans":[]},{"type":"paragraph","text":"    php artisan serve","spans":[]},{"type":"preformatted","text":"`}```","spans":[]},{"type":"image","url":"https://images.prismic.io/www-static/67fc2310-14e8-420c-b837-1c583b84708b_Kamal-2.png?auto=compress,format","alt":null,"copyright":null,"dimensions":{"width":1600,"height":869}},{"type":"paragraph","text":"Let's think about what our app will need. We want to have a list of contacts, so the database will store them. This means that we will need a ```[php]{`Contact`}``` model and a migration for the ```[php]{`contacts`}``` table. We'll also want to initialize the database with some random contacts so we have something to see, which will require a ```[php]{`Contact`}``` Factory and Seeder. ","spans":[]},{"type":"paragraph","text":"Let's build them out step by step:","spans":[]},{"type":"heading2","text":"Step 2: Create the Contact Model","spans":[]},{"type":"paragraph","text":"As shown in the screenshot above, a contact will have the following properties:","spans":[]},{"type":"list-item","text":"Name","spans":[]},{"type":"list-item","text":"Phone Number","spans":[]},{"type":"list-item","text":"Address","spans":[]},{"type":"list-item","text":"Favorite Colors","spans":[]},{"type":"paragraph","text":"For the favorite colors property, we will make use of Postgres's JSON data type. We will create a generic \"favorites\" column that will contain a JSON object with a list of favorite things. In this post we will store favorite colors only, but using a generic “favorites” object allows us to add different types in the future.","spans":[]},{"type":"paragraph","text":"Generate a model, factory, and migration using ```[php]{`artisan`}```:","spans":[]},{"type":"preformatted","text":"```[php]{`","spans":[]},{"type":"paragraph","text":"    php artisan make:model -f -m Contact","spans":[]},{"type":"preformatted","text":"`}```","spans":[]},{"type":"paragraph","text":"Now we'll configure the different parts of the model.","spans":[]},{"type":"heading3","text":"Migration","spans":[]},{"type":"paragraph","text":"Edit the generated migration file stored in ```[php]{`database/migrations/*_create_contacts_table.php`}```. Inside the ```[php]{`Schema::create()`}``` block, we will define the structure of the table:","spans":[]},{"type":"preformatted","text":"```[php]{`","spans":[]},{"type":"paragraph","text":"    <?php  ","spans":[]},{"type":"paragraph","text":"    use Illuminate\\Support\\Facades\\Schema;  ","spans":[]},{"type":"paragraph","text":"    use Illuminate\\Database\\Schema\\Blueprint;  ","spans":[]},{"type":"paragraph","text":"    use Illuminate\\Database\\Migrations\\Migration;  ","spans":[]},{"type":"paragraph","text":"    class CreateContactsTable extends Migration  ","spans":[]},{"type":"paragraph","text":"    {","spans":[]},{"type":"paragraph","text":"        /**","spans":[]},{"type":"paragraph","text":"          * Run the migrations.","spans":[]},{"type":"paragraph","text":"          *","spans":[]},{"type":"paragraph","text":"          * @return void","spans":[]},{"type":"paragraph","text":"          */","spans":[]},{"type":"paragraph","text":"        public function up()","spans":[]},{"type":"paragraph","text":"        {","spans":[]},{"type":"paragraph","text":"            Schema::create('contacts', function (Blueprint $table) {","spans":[]},{"type":"paragraph","text":"                $table->increments('id');","spans":[]},{"type":"paragraph","text":"                $table->string('name');","spans":[]},{"type":"paragraph","text":"                $table->string('phone');","spans":[]},{"type":"paragraph","text":"                $table->string('address');","spans":[]},{"type":"paragraph","text":"                $table->jsonb('favorites')->default('{}');","spans":[]},{"type":"paragraph","text":"                $table->timestamps();","spans":[]},{"type":"paragraph","text":"            });","spans":[]},{"type":"paragraph","text":"        }","spans":[]},{"type":"paragraph","text":"        /**","spans":[]},{"type":"paragraph","text":"            * Reverse the migrations.","spans":[]},{"type":"paragraph","text":"            *","spans":[]},{"type":"paragraph","text":"            * @return void","spans":[]},{"type":"paragraph","text":"            */","spans":[]},{"type":"paragraph","text":"        public function down()","spans":[]},{"type":"paragraph","text":"        {","spans":[]},{"type":"paragraph","text":"            Schema::dropIfExists('contacts');","spans":[]},{"type":"paragraph","text":"        }","spans":[]},{"type":"paragraph","text":"    }","spans":[]},{"type":"preformatted","text":"`}```","spans":[]},{"type":"paragraph","text":"This will configure the database column as described above with the `favorites` column set to a JSON object. The default value is an empty object.","spans":[]},{"type":"heading3","text":"Random Contact Generation","spans":[]},{"type":"paragraph","text":"The contact factory generates random values for a contact. We will configure a seeder that will use the factory to insert 10 random contacts to the database. This will provide us with an option to seed the database with random contacts when running the migration. Let's start with the factory.","spans":[]},{"type":"heading4","text":"Factory","spans":[]},{"type":"paragraph","text":"Edit the generated factory file stored in ```[php]{`database/factories/ContactFactory.php`}```. First, we will generate three random colors to use as favorites:","spans":[]},{"type":"preformatted","text":"```[php]{`","spans":[]},{"type":"paragraph","text":"    $colors = collect(range(1, 3))->map(function() use ($faker) {","spans":[]},{"type":"paragraph","text":"        return $faker->colorName;","spans":[]},{"type":"paragraph","text":"    })->toArray();","spans":[]},{"type":"preformatted","text":"`}```","spans":[]},{"type":"paragraph","text":"We will use these colors along with other data generated using the Faker library to return the contact's properties:","spans":[]},{"type":"preformatted","text":"```[php]{`","spans":[]},{"type":"paragraph","text":"    return [","spans":[]},{"type":"paragraph","text":"        'name' => $faker->name,","spans":[]},{"type":"paragraph","text":"        'phone' => $faker->e164PhoneNumber,","spans":[]},{"type":"paragraph","text":"        'address' => $faker->address,","spans":[]},{"type":"paragraph","text":"        'favorites' => ['colors' => $colors],","spans":[]},{"type":"paragraph","text":"    ];","spans":[]},{"type":"preformatted","text":"`}```","spans":[]},{"type":"paragraph","text":"The resulting factory should look like this:","spans":[]},{"type":"preformatted","text":"```[php]{`","spans":[]},{"type":"paragraph","text":"    <?php","spans":[]},{"type":"paragraph","text":"    ","spans":[]},{"type":"paragraph","text":"    use Faker\\Generator as Faker;","spans":[]},{"type":"paragraph","text":"    ","spans":[]},{"type":"paragraph","text":"    $factory->define(App\\Contact::class, function (Faker $faker) {","spans":[]},{"type":"paragraph","text":"        // generate 3 random colors","spans":[]},{"type":"paragraph","text":"        $colors = collect(range(1, 3))->map(function() use ($faker) {","spans":[]},{"type":"paragraph","text":"            return $faker->colorName;","spans":[]},{"type":"paragraph","text":"        })->toArray();","spans":[]},{"type":"paragraph","text":"    ","spans":[]},{"type":"paragraph","text":"        return [","spans":[]},{"type":"paragraph","text":"            'name' => $faker->name,","spans":[]},{"type":"paragraph","text":"            'phone' => $faker->e164PhoneNumber,","spans":[]},{"type":"paragraph","text":"            'address' => $faker->address,","spans":[]},{"type":"paragraph","text":"            'favorites' => ['colors' => $colors],","spans":[]},{"type":"paragraph","text":"        ];","spans":[]},{"type":"paragraph","text":"    });","spans":[]},{"type":"preformatted","text":"`}```","spans":[]},{"type":"heading4","text":"Seeder","spans":[]},{"type":"paragraph","text":"Now we need to define a seeder that will be run upon migrating the database. Start by generating the file:","spans":[]},{"type":"paragraph","text":"    ```[php]{`php artisan make:seeder ContactsTableSeeder`}```","spans":[]},{"type":"paragraph","text":"Open the generated file ```[php]{`database/seeds/ContactsTableSeeder.php`}``` in an editor and call the factory inside the ```[php]{`run()`}``` function like so:","spans":[]},{"type":"preformatted","text":"```[php]{`","spans":[]},{"type":"paragraph","text":"    <?php","spans":[]},{"type":"paragraph","text":"    use Illuminate\\Database\\Seeder;","spans":[]},{"type":"paragraph","text":"    class ContactsTableSeeder extends Seeder","spans":[]},{"type":"paragraph","text":"    {","spans":[]},{"type":"paragraph","text":"        /**","spans":[]},{"type":"paragraph","text":"         * Run the database seeds.","spans":[]},{"type":"paragraph","text":"         *","spans":[]},{"type":"paragraph","text":"         * @return void","spans":[]},{"type":"paragraph","text":"         */","spans":[]},{"type":"paragraph","text":"        public function run()","spans":[]},{"type":"paragraph","text":"        {","spans":[]},{"type":"paragraph","text":"            factory(App\\Contact::class, 10)->create();","spans":[]},{"type":"paragraph","text":"        }","spans":[]},{"type":"paragraph","text":"    }","spans":[]},{"type":"preformatted","text":"`}```","spans":[]},{"type":"paragraph","text":"To enable the seeder, edit `database/seeds/DatabaseSeeder.php` and call it inside the `run()` function:","spans":[]},{"type":"preformatted","text":"```[php]{`","spans":[]},{"type":"paragraph","text":"    <?php","spans":[]},{"type":"paragraph","text":"    use Illuminate\\Database\\Seeder;","spans":[]},{"type":"paragraph","text":"    class DatabaseSeeder extends Seeder","spans":[]},{"type":"paragraph","text":"    {","spans":[]},{"type":"paragraph","text":"        /**","spans":[]},{"type":"paragraph","text":"         * Seed the application's database.","spans":[]},{"type":"paragraph","text":"         *","spans":[]},{"type":"paragraph","text":"         * @return void","spans":[]},{"type":"paragraph","text":"         */","spans":[]},{"type":"paragraph","text":"        public function run()","spans":[]},{"type":"paragraph","text":"        {","spans":[]},{"type":"paragraph","text":"            $this->call(ContactsTableSeeder::class);","spans":[]},{"type":"paragraph","text":"        }","spans":[]},{"type":"paragraph","text":"    }","spans":[]},{"type":"preformatted","text":"`}```","spans":[]},{"type":"heading3","text":"Model","spans":[]},{"type":"paragraph","text":"There is one final adjustment that we need to make to the model. The ```[php]{`artisan make:model`}``` command generated a model file for us, located in ```[php]{`app/Contact.php`}```. The `favorites` column represents a JSON object, so we need to cast it to a PHP array before using it. Eloquent makes this very easy by automatically casting back and forth between the correct formats. Inside the ```[php]{`Contact`}``` class in ```[php]{`app/Contact.php`}```, we can add the cast:","spans":[]},{"type":"preformatted","text":"```[php]{`","spans":[]},{"type":"paragraph","text":"    <?php","spans":[]},{"type":"paragraph","text":"    namespace App;","spans":[]},{"type":"paragraph","text":"    use Illuminate\\Database\\Eloquent\\Model;","spans":[]},{"type":"paragraph","text":"    class Contact extends Model","spans":[]},{"type":"paragraph","text":"    {","spans":[]},{"type":"paragraph","text":"        protected $casts = [","spans":[]},{"type":"paragraph","text":"            'favorites' => 'array',","spans":[]},{"type":"paragraph","text":"        ];","spans":[]},{"type":"paragraph","text":"    }","spans":[]},{"type":"preformatted","text":"`}```","spans":[]},{"type":"heading2","text":"Step 3: Configure the Database","spans":[]},{"type":"paragraph","text":"We will use DigitalOcean Databases for our PostgreSQL cluster. If you haven’t yet, create a new one—it only takes a few minutes. If you prefer a text post, see the product documentation for Databases. If you prefer a video, click here.","spans":[{"start":160,"end":199,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/docs/databases/how-to/clusters/create/"}},{"start":224,"end":234,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.youtube.com/watch?v=jY5FhyiEdig"}}]},{"type":"image","url":"https://images.prismic.io/www-static/e80ddc5c-b52d-42a0-a6b9-faf3e0a10a51_Kamal-3.png?auto=compress,format","alt":null,"copyright":null,"dimensions":{"width":1600,"height":427}},{"type":"paragraph","text":"In the cluster’s Overview page in the control panel, get its connection details as “Connection parameters”. Open Laravel's ```[php]{`.env`}``` file and set ```[php]{`DB_CONNECTION=pgsql`}```. Below it, set all the other variables according to your connection credentials.","spans":[]},{"type":"image","url":"https://images.prismic.io/www-static/3f4ec974-28d2-46ca-80b3-18a8461abca2_Kamal-4.png?auto=compress,format","alt":null,"copyright":null,"dimensions":{"width":990,"height":540}},{"type":"paragraph","text":"There is one setting that isn’t available as an environment variable: ```[php]{`sslmode`}```. DigitalOcean Databases do not support non-TLS connections so we need to set ```[php]{`sslmode`}``` to ```[php]{`require`}```. Open ```[php]{`config/database.php`}``` in an editor, scroll down to the ```[php]{`pgsql`}``` definition, and update the setting like so:","spans":[]},{"type":"preformatted","text":"```[php]{`","spans":[]},{"type":"paragraph","text":"    'pgsql' => [","spans":[]},{"type":"paragraph","text":"        ...","spans":[]},{"type":"paragraph","text":"        'sslmode' => 'require',","spans":[]},{"type":"paragraph","text":"    ],","spans":[]},{"type":"preformatted","text":"`}```","spans":[]},{"type":"paragraph","text":"[Related: Check out our Resource Center for resources and guides on Managed Databases]","spans":[{"start":0,"end":86,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/resources/managed-databases/"}}]},{"type":"heading2","text":"Step 4: Migrate and seed","spans":[]},{"type":"paragraph","text":"Now that we have completed defining everything database-related in our app, from the Contact model to the database connection info, we can execute the migration and seed the database:","spans":[]},{"type":"preformatted","text":"```[php]{`","spans":[]},{"type":"paragraph","text":"    php artisan migrate --seed","spans":[]},{"type":"preformatted","text":"`}```","spans":[]},{"type":"paragraph","text":"We have a database filled with contacts now—nice!  ","spans":[]},{"type":"heading2","text":"Step 4: Build the Contact List Page","spans":[]},{"type":"paragraph","text":"Ok, let's finish up by creating a page to show our contacts.","spans":[]},{"type":"paragraph","text":"To keep things simple we will replace Laravel's default home page. Edit `routes/web.php` and set the `/` route to the following:","spans":[]},{"type":"preformatted","text":"```[php]{`","spans":[]},{"type":"paragraph","text":"    Route::get('/', function () {","spans":[]},{"type":"paragraph","text":"        $contacts = App\\Contact::all();","spans":[]},{"type":"paragraph","text":"        return view('welcome', ['contacts' => $contacts]);","spans":[]},{"type":"paragraph","text":"    });","spans":[]},{"type":"preformatted","text":"`}```","spans":[]},{"type":"paragraph","text":"This will fetch all the contacts from the database and pass them to the ```[php]{`welcome`}``` view.","spans":[]},{"type":"paragraph","text":"Then, edit the view located in `resources/views/welcome.blade.php` and replace its contents with the following:","spans":[]},{"type":"preformatted","text":"```[php]{`","spans":[]},{"type":"paragraph","text":"    <!doctype html>","spans":[]},{"type":"paragraph","text":"    <html>","spans":[]},{"type":"paragraph","text":"        <head>","spans":[]},{"type":"paragraph","text":"            <meta charset=\"utf-8\">","spans":[]},{"type":"paragraph","text":"            <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">","spans":[]},{"type":"paragraph","text":"    ","spans":[]},{"type":"paragraph","text":"            <title>Contacts</title>","spans":[]},{"type":"paragraph","text":"            <link rel=\"stylesheet\" href=\"https://unpkg.com/tachyons@4.10.0/css/tachyons.min.css\"/>","spans":[]},{"type":"paragraph","text":"        </head>","spans":[]},{"type":"paragraph","text":"        <body>","spans":[]},{"type":"paragraph","text":"            <div class=\"mw6 center pa3 sans-serif\">","spans":[]},{"type":"paragraph","text":"                <h1 class=\"mb4\">Contacts</h1>","spans":[]},{"type":"paragraph","text":"    ","spans":[]},{"type":"paragraph","text":"                @foreach($contacts as $contact)","spans":[]},{"type":"paragraph","text":"                <div class=\"pa2 mb3 striped--near-white\">","spans":[]},{"type":"paragraph","text":"                    <header class=\"b mb2\">{{ $contact->name }}</header>","spans":[]},{"type":"paragraph","text":"                    <div class=\"pl2\">","spans":[]},{"type":"paragraph","text":"                        <p class=\"mb2\">{{ $contact->phone }}</p>","spans":[]},{"type":"paragraph","text":"                        <p class=\"pre mb3\">{{ $contact->address }}</p>","spans":[]},{"type":"paragraph","text":"                        <p class=\"mb2\"><span class=\"fw5\">Favorite colors:</span> {{ implode(', ', $contact->favorites['colors']) }}</p>","spans":[]},{"type":"paragraph","text":"                    </div>","spans":[]},{"type":"paragraph","text":"                </div>","spans":[]},{"type":"paragraph","text":"                @endforeach","spans":[]},{"type":"paragraph","text":"            </div>","spans":[]},{"type":"paragraph","text":"        </body>","spans":[]},{"type":"paragraph","text":"    </html>","spans":[]},{"type":"preformatted","text":"`}```","spans":[]},{"type":"heading2","text":"Step 5: Deploy","spans":[]},{"type":"paragraph","text":"We're all done! Let's go ahead and try out our app.","spans":[]},{"type":"paragraph","text":"Like before, we can use Laravel's built-in web server. Simply run ```[php]{`php artisan serve`}``` and browse to http://127.0.0.1:8000.","spans":[{"start":113,"end":134,"type":"hyperlink","data":{"link_type":"Web","url":"http://127.0.0.1:8000/"}}]},{"type":"heading3","text":"Deploy on a Droplet","spans":[]},{"type":"paragraph","text":"To deploy the app on a Droplet, follow the How To Deploy a Laravel Application with Nginx on Ubuntu 16.04 guide on the DigitalOcean community.","spans":[{"start":43,"end":105,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/how-to-deploy-a-laravel-application-with-nginx-on-ubuntu-16-04"}}]},{"type":"heading2","text":"Conclusion","spans":[]},{"type":"paragraph","text":"In this post, we went through building a simple contacts list step by step and explored a few concepts such as seeding the database with sample random data, using JSON-type database fields, and connecting to a managed PostgreSQL Database.","spans":[]},{"type":"paragraph","text":"Here are a few things you can do following this post, building on the app we created:","spans":[]},{"type":"list-item","text":"Add a favorite shape (square, circle, etc.) to one or more contacts and edit the view template to display it. Update the seeder to include a random shape at random, or for every contact.","spans":[]},{"type":"list-item","text":"Extract the route handler (in ```[php]{`web.php`}```) into its own controller. Use ```[php]{`artisan make:controller`}``` to create the controller file. Consult the Laravel documentation on controllers for more details.","spans":[]},{"type":"list-item","text":"List the contacts in the order that they were last updated, most recent first.","spans":[]},{"type":"paragraph","text":"[Hungry for another tutorial? Try Kamal's guide to \"Deploying a Fully-automated Git-based Static Website in Under 5 Minutes\"]","spans":[{"start":0,"end":125,"type":"hyperlink","data":{"link_type":"Web","url":"https://blog.digitalocean.com/deploying-a-fully-automated-git-based-static-website-in-under-5-minutes/"}}]},{"type":"paragraph","text":"Kamal Nasser is a Developer Advocate at DigitalOcean. He is also a Computer Science student with a passion for software engineering and avocados. You can find him on Twitter @kamaln7.","spans":[{"start":0,"end":183,"type":"em"},{"start":174,"end":182,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/kamaln7"}}]}],"blog_post_date":"2019-02-21","tags":[{"tag1":{"tag":"Developer Relations","_linkType":"Link.document","_meta":{"uid":"developer-relations"}}},{"tag1":{"tag":"Community","_linkType":"Link.document","_meta":{"uid":"community"}}}],"_meta":{"uid":"create-simple-contacts-laravel-postgresql"}}},{"node":{"author":{"_linkType":"Link.document","author_name":"Community Team","author_image":null,"_meta":{"uid":"community_team"}},"blog_header_image":{"dimensions":{"width":1200,"height":600},"alt":"scuba divers on computers illustration","copyright":null,"url":"https://images.prismic.io/www-static/03fe1bea1594de6d547c1b8fefc433f980ef7fc6_pathwaystechnicalwriting-final.png?auto=compress,format"},"blog_headline":[{"type":"heading1","text":"Becoming a Technical Writer: The Paths 3 Engineers Took to their First Community Tutorial","spans":[]}],"blog_post_content":[{"type":"paragraph","text":"Regular readers of DigitalOcean Community tutorials (like How To Secure Apache with Let's Encrypt on Ubuntu 18.04 or How To Spin Up a Hadoop Cluster with DigitalOcean Droplets) may have noticed that beyond setup instructions, tutorials often contain insights, tips, and pain points surfaced from real-world production scenarios. This is because many of them are written by DevOps engineers, sysadmins, and software developers eager to share solutions and workarounds to problems faced while rolling out software and systems on the job.","spans":[{"start":58,"end":113,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/how-to-secure-apache-with-let-s-encrypt-on-ubuntu-18-04"}},{"start":117,"end":175,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/how-to-spin-up-a-hadoop-cluster-with-digitalocean-droplets"}}]},{"type":"paragraph","text":"Through tutorials, DigitalOcean writers contribute their in-the-trenches experience to the wider developer community, and in doing so solidify their own understanding of technical concepts. Some authors work full-time on the DigitalOcean Community writing team, while others contribute tutorials as part of the Write for DOnations program, which matches the author's payout with a charitable donation to a tech-focused nonprofit. All Community contributors share a common spirit of “giving back” to readers through teaching, whether these readers are seasoned engineering managers, or students wading in the waters of setting up an Nginx server or Jupyter Notebook for the first time.","spans":[{"start":311,"end":330,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/write-for-donations/"}},{"start":618,"end":644,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-18-04"}},{"start":648,"end":664,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/how-to-set-up-jupyter-notebook-for-python-3"}}]},{"type":"paragraph","text":"DigitalOcean tutorials can broadly be categorized as either conceptual or procedural. An Introduction to Kubernetes, for example, is more conceptual in nature, as the author provides the reader with an overview of a piece of software or DevOps concept and boils it down to a set of digestible core ideas. How To Set Up an Elasticsearch, Fluentd, and Kibana (EFK) Logging Stack on Kubernetes, on the other hand, is a procedural walkthrough to support a developer setting up their infrastructure. The tutorial brings the reader through the installation and configuration of one or several technologies step-by-step, often providing valuable insight and context along the way.","spans":[{"start":86,"end":115,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/an-introduction-to-kubernetes"}},{"start":305,"end":390,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/how-to-set-up-an-elasticsearch-fluentd-and-kibana-efk-logging-stack-on-kubernetes"}}]},{"type":"heading3","text":"Becoming a Technical Writer","spans":[]},{"type":"paragraph","text":"There is no “one” path to becoming a DigitalOcean tutorial writer. Mitchell Anicas, formerly a senior technical writer on the Community team, and now a senior software engineer on the Billing team, began his career as a systems administrator at the University of Hawaii. After relocating to New York, he leveraged his years of experience administering systems and automating their configuration and deployment to begin writing Linux and infrastructure tutorials full-time.","spans":[{"start":67,"end":82,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/users/manicas"}}]},{"type":"paragraph","text":"“While working as a sysadmin, many of the tutorials I referenced weren’t complete, or weren’t really that high quality. It was a lot of someone writing blog posts saying something like ‘this is how this worked for me,’” he says. “Since I had been on the other side, I had a lot of empathy for readers.” Although he’d never written a tutorial before, with the help of other Community writers (all tutorials are peer-edited and tech-tested by another member of the Community team), he began publishing articles like How To Install Elasticsearch, Logstash, and Kibana (ELK Stack) on Ubuntu 14.04 and 5 Common Server Setups For Your Web Application.","spans":[{"start":514,"end":592,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/how-to-install-elasticsearch-logstash-and-kibana-elk-stack-on-ubuntu-14-04"}},{"start":597,"end":644,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/5-common-server-setups-for-your-web-application"}}]},{"type":"paragraph","text":"On the other hand, Erika Heidi, a software engineer and writer based out of Amsterdam, had always been a writer. “I’ve always enjoyed writing, since I was very young,” she says. “I figured out that I could use blogging as a platform for documenting technical things like setting up servers and fixing common Linux problems, both as a future reference for myself and also as a way to share what I was learning.”","spans":[{"start":19,"end":30,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/users/erikaheidi"}}]},{"type":"paragraph","text":"As a Community author, she’s contributed many tutorials, ranging from the procedural How To Secure Apache with Let's Encrypt on Ubuntu 16.04, to conceptual articles like What is High Availability and An Introduction to Configuration Management that draw from her extensive experience as a DevOps engineer. Like Mitchell, her technical experience endowed her with a keen sense for the problems her peers were facing and solutions she could provide to fill the gap.","spans":[{"start":85,"end":140,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/how-to-secure-apache-with-let-s-encrypt-on-ubuntu-16-04"}},{"start":170,"end":195,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/what-is-high-availability"}},{"start":200,"end":243,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/an-introduction-to-configuration-management"}}]},{"type":"paragraph","text":"Jeremy Morris followed a similar path to publishing his first DigitalOcean tutorial: “Throughout college as a computer science student, I would occasionally write blog posts about some of the things I learned at my internships, as a way of gaining a deeper understanding of the topics and sharing my knowledge with others,” he recalls. One of his professors, Lisa Tagliaferri, now managing the team of in-house Community writers at DigitalOcean, recommended he leverage his newly gained Python and Django experience and write a tutorial series on building a blog. This eventually led to him publishing Django tutorials like How To Install Django and Set Up a Development Environment on Ubuntu 16.04, How To Create a Django App and Connect it to a Database and How To Create Django Models, all topics he had become familiar with through his professional work.","spans":[{"start":0,"end":13,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/users/jeremylevanmorris"}},{"start":624,"end":698,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/how-to-install-django-and-set-up-a-development-environment-on-ubuntu-16-04"}},{"start":700,"end":755,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/how-to-create-a-django-app-and-connect-it-to-a-database"}},{"start":760,"end":787,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/how-to-create-django-models"}}]},{"type":"paragraph","text":"Mitchell, Erika, and Jeremy all became writers at different stages of their careers in tech. Their common desire to help their peers through sharing hard-won solutions to challenging DevOps problems led them to publish their first DigitalOcean tutorials. While writing and educating others presented them with the need to develop distinct communication skills, they found that the challenge of describing solutions in a clear and accessible manner was well worth developing alongside their engineering experience.","spans":[]},{"type":"heading3","text":"Why Write?","spans":[]},{"type":"paragraph","text":"Technical writing can be a powerful complement to engineering work, requiring the author to understand and process concepts at a deeper level than may be required to complete day-to-day tasks. Erika, in her drive to deeply understand the technologies she works with as a DevOps engineer, finds that writing tutorials helps her truly gain familiarity with new ideas: “There’s a lot of stuff in engineering I know how to do because I’ve done it multiple times before and it simply works, but if I had to explain how it works, I couldn’t,” she notes. “Writing helps me ‘untangle’ my thoughts, because I have to explain and organize those thoughts into logical steps.”","spans":[]},{"type":"paragraph","text":"It can also be an incredibly rewarding pursuit. By publishing open-source tech tutorials, Erika feels that she is helping others who have similarly lent a hand throughout her learning path: “I believe the fulfillment comes from the feeling that I’m sharing something that might be useful for others. I’m searching for tutorials and how to do stuff all the time on the internet, and this is one way I can give back to the community.”","spans":[]},{"type":"paragraph","text":"Mitchell shares this sense of being able to contribute his experience and knowledge through tutorials: “It’s cool to be able to help thousands of people through writing, especially writing tutorials for DigitalOcean, whose tutorials are respected and recognized by the developer community at large. I’ll be at a conference and run into someone and they’ll say something like ‘Oh yeah I used that tutorial of yours to set up an ELK stack, thanks so much!’”","spans":[]},{"type":"heading3","text":"Taking the Plunge and Diving into the Technical Writing","spans":[]},{"type":"paragraph","text":"So how do you get started writing your first DevOps, software development, or systems tutorial? By jumping into the deep end, of course! Through the Write for DOnations program you can submit a short writing sample (for inspiration, take a look at this list of suggested topics) and work with our Community editors to have your article edited, tech-tested, proofread, and guided to publication. In addition, you'll be paid for your work, and DigitalOcean will match your payout with a donation to a tech-focused charity of your choice. To date, DigitalOcean has donated over $13,000 through external-author submissions and the Write for DOnations program!","spans":[{"start":149,"end":168,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/write-for-donations/"}},{"start":248,"end":257,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/suggested-topics-for-tutorials"}},{"start":499,"end":519,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/write-for-donations-faq#which-charities-and-nonprofits-will-my-writing-support"}}]},{"type":"paragraph","text":"In addition, the Community team frequently has openings for new writers, editors, and developer advocates to educate, curate, and produce some of the highest quality software-focused tutorials on the web. If you’re a DevOps or software engineer and have some experience writing documentation or other content, consult our Careers page for an up-to-date list of open full-time Community positions.","spans":[{"start":322,"end":329,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/careers/"}}]},{"type":"paragraph","text":"Although he may have been referring to something other than the pains of backing up and replicating a large distributed MySQL database, let Hemingway’s words guide you as you set sail on your technical writing voyage: “write hard and clear about what hurts.”","spans":[]}],"blog_post_date":"2019-02-19","tags":[{"tag1":{"tag":"Community","_linkType":"Link.document","_meta":{"uid":"community"}}}],"_meta":{"uid":"how-three-engineers-wrote-their-first-community-tutorials"}}},{"node":{"author":{"_linkType":"Link.document","author_name":"Community Team","author_image":null,"_meta":{"uid":"community_team"}},"blog_header_image":{"dimensions":{"width":799,"height":433},"alt":"People on a boat illustration","copyright":null,"url":"https://images.prismic.io/www-static/fba651e193b42ae8b36a402a7e5daa04949d87e6_dribbble_boat-1.png?auto=compress,format"},"blog_headline":[{"type":"heading1","text":"Here's A Round-Up of 2018 Community Tutorials","spans":[]}],"blog_post_content":[{"type":"paragraph","text":"2018 has been an exciting year for us on the DigitalOcean Community team as we surpassed over 2,000 tutorials in our repository in September!","spans":[{"start":94,"end":109,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials"}}]},{"type":"paragraph","text":"We continue to work with community authors to publish tutorials that serve developers. In February of this year we launched our Write for DOnations program, which not only adds to our Community knowledge base, but also gives back through making a donation to tech-focused nonprofits. If you are thinking about sharing your knowledge with the developer community, consider applying to this program so you can begin working with Community Editors who will offer you mentorship as you write your tutorial.","spans":[{"start":128,"end":147,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/write-for-donations/"}},{"start":259,"end":282,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/write-for-donations-faq#which-charities-and-nonprofits-will-my-writing-support"}},{"start":372,"end":396,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/write-for-donations/#anchor--apply-now"}}]},{"type":"paragraph","text":"This year, we also released new learning resources. DigitalOcean Blueprints offer automated multi-server infrastructure setups to support you as you scale. Our first Community white paper, Running Cloud Native Applications on DigitalOcean Kubernetes, provides guidance for you and your team as you get up and running with Kubernetes.","spans":[{"start":52,"end":75,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/digitalocean-blueprints-getting-up-and-running-with-node-js-mysql-replication-and-cachet"}},{"start":189,"end":249,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/white-paper-running-cloud-native-applications-on-digitalocean-kubernetes"}}]},{"type":"paragraph","text":"Additionally, we have begun to offer tutorials to help you with automating your server setups, and have worked to keep tutorials up-to-date through maintenance, with pushes for both the Ubuntu 18.04 and Debian 9 updates.","spans":[{"start":64,"end":93,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/automating-initial-server-setup-with-ubuntu-18-04"}}]},{"type":"paragraph","text":"We selected a few tutorials below that showcase some of the breadth and depth that we and our community wrote about this year: from building neural networks to architecting applications for Kubernetes.","spans":[]},{"type":"list-item","text":"Modernizing Applications for Kubernetes","spans":[{"start":0,"end":39,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/modernizing-applications-for-kubernetes"}}]},{"type":"list-item","text":"In this conceptual guide by Hanif Jetha, learn about the high-level steps you need to follow for modernizing your applications so that you can ultimately run and manage them in a Kubernetes cluster.","spans":[]},{"type":"list-item","text":"How To Build a Modern Web Application to Manage Customer Information with Django and React on Ubuntu 18.04","spans":[{"start":0,"end":106,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/how-to-build-a-modern-web-application-to-manage-customer-information-with-django-and-react-on-ubuntu-18-04"}}]},{"type":"list-item","text":"Build a modern web application with a separate REST API backend and frontend using React, Django, and the Django REST Framework by going through this tutorial by Ahmed Bouchefra.","spans":[]},{"type":"list-item","text":"How To Build a Neural Network to Recognize Handwritten Digits with TensorFlow","spans":[{"start":0,"end":77,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/how-to-build-a-neural-network-to-recognize-handwritten-digits-with-tensorflow"}}]},{"type":"list-item","text":"Neural networks, used as a method of deep learning, attempt to simulate the way the human brain works. This tutorial by Ellie Birbeck will walk you through implementing a subsection of object recognition by using TensorFlow, an open-source Python library.","spans":[]},{"type":"list-item","text":"How To Manage an SQL Database","spans":[{"start":0,"end":29,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/how-to-manage-sql-database-cheat-sheet"}}]},{"type":"list-item","text":"For anyone who has ever desired a quick reference to some of the most commonly-used SQL commands, Mark Drake delivers in this cheat sheet tutorial.","spans":[]},{"type":"list-item","text":"Kubernetes Networking Under the Hood","spans":[{"start":0,"end":36,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/kubernetes-networking-under-the-hood"}}]},{"type":"list-item","text":"Brian Boucheron breaks down how Kubernetes networking works within a cluster, including how data moves inside a pod, between pods, and between nodes.","spans":[]},{"type":"list-item","text":"How To Use Alertmanager And Blackbox Exporter To Monitor Your Web Server On Ubuntu 16.04","spans":[{"start":0,"end":88,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/how-to-use-alertmanager-and-blackbox-exporter-to-monitor-your-web-server-on-ubuntu-16-04"}}]},{"type":"list-item","text":"Sending alerts when problems arise significantly speeds up identifying the root cause of an issue and helps you recover quickly. Marko Mudrinić’s tutorial covers Alertmanager and Blackbox Exporter to monitor the responsiveness of an Nginx web server, allowing you to send e-mail and Slack notifications if your server isn't responding.","spans":[]},{"type":"list-item","text":"DigitalOcean eBook: How To Code in Python","spans":[{"start":0,"end":41,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/digitalocean-ebook-how-to-code-in-python"}}]},{"type":"list-item","text":"Not quite a single tutorial, but our free eBook that collects together Lisa Tagliaferri’s more than 30 Python tutorials so you can learn how to code in Python or reference Python syntax while you’re on the go.","spans":[]},{"type":"list-item","text":"How to Use Traefik as a Reverse Proxy for Docker Containers on Ubuntu 18.04","spans":[{"start":0,"end":75,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/how-to-use-traefik-as-a-reverse-proxy-for-docker-containers-on-ubuntu-18-04"}}]},{"type":"list-item","text":"When you are trying to run multiple applications on the same host, you’ll need to set up a reverse proxy. Traefik is a Docker-aware reverse proxy solution offering its own monitoring dashboard. Learn how to use Traefik to route requests to two different web application containers by following this tutorial by Keith Thompson.","spans":[]},{"type":"list-item","text":"How to Automatically Deploy Laravel Applications with Deployer on Ubuntu 16.04","spans":[{"start":0,"end":78,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/automatically-deploy-laravel-applications-deployer-ubuntu"}}]},{"type":"list-item","text":"Laravel is an open-source PHP web framework designed to make common web development tasks easier. András Magyar walks you through deploying a Laravel application automatically without any downtime.","spans":[]},{"type":"list-item","text":"How To Build a Node.js Application with Docker","spans":[{"start":0,"end":46,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/how-to-build-a-node-js-application-with-docker"}}]},{"type":"list-item","text":"Recreate and scale your Node.js application with Docker by following this tutorial by Kathleen Juell.","spans":[]},{"type":"list-item","text":"How to Deploy a Symfony 4 Application to Production with LEMP on Ubuntu 18.04","spans":[{"start":0,"end":77,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/how-to-deploy-a-symfony-4-application-to-production-with-lemp-on-ubuntu-18-04"}}]},{"type":"list-item","text":"Deploy a Symfony 4 application to production with a LEMP stack on Ubuntu 18.04 to get started configuring the server and the structure of the framework. Oluyemi Olususi’s tutorial will take you through all the steps you need.","spans":[]},{"type":"list-item","text":"Architecting Applications for Kubernetes","spans":[{"start":0,"end":40,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/architecting-applications-for-kubernetes"}}]},{"type":"list-item","text":"Designing and running applications with scalability, portability, and robustness in mind can be challenging, especially as system complexity grows. In Justin Ellingwood’s guide, learn some of the principles and patterns you can adopt to help you scale and manage your workloads on Kubernetes.","spans":[]}],"blog_post_date":"2018-12-20","tags":[{"tag1":{"tag":"Community","_linkType":"Link.document","_meta":{"uid":"community"}}}],"_meta":{"uid":"2018-community-tutorials-roundups"}}},{"node":{"author":{"_linkType":"Link.document","author_name":"Daniel Zaltsman","author_image":{"dimensions":{"width":188,"height":188},"alt":"Daniel Zaltsman","copyright":null,"url":"https://images.prismic.io/www-static/663d428f56c46eeb165c811add8f1f60402aa451_daniel_zaltsman-c47f4847.png?auto=compress,format"},"_meta":{"uid":"daniel_zaltsman"}},"blog_header_image":{"dimensions":{"width":784,"height":418},"alt":"Hacktoberfest H with a Woman developer sitting on the end of the H working","copyright":null,"url":"https://images.prismic.io/www-static/ef44e76db7aad43808d319f25fe2865d6020de35_hacktoberfest_blog.png?auto=compress,format"},"blog_headline":[{"type":"heading1","text":"A Review of Hacktoberfest Year 5!","spans":[]}],"blog_post_content":[{"type":"paragraph","text":"Five years ago the community team at DigitalOcean wanted to create a program to inspire open source contributions. That first year, in 2014, the first Hacktoberfest participants were asked for 50 commits, and those who completed the challenge received a reward of swag. 676 people signed up and 505 forged ahead to the finish line, earning stickers and a custom limited-edition T-shirt.","spans":[{"start":141,"end":164,"type":"hyperlink","data":{"link_type":"Web","url":"https://blog.digitalocean.com/hacktoberfest/"}},{"start":270,"end":273,"type":"strong"},{"start":295,"end":298,"type":"strong"}]},{"type":"paragraph","text":"This year that number is an astounding 46,088 completions out of 106,582 sign-ups. We’ve seen it become an entry point to developers contributing to open source projects: much more than a program, it’s clear that Hacktoberfest has become a global community movement with a shared set of values and passion for giving back. It’s been a pleasure to be behind the scenes watching it grow every year, and in this post we’ll share what we saw from all of you in the 5th anniversary.","spans":[{"start":39,"end":45,"type":"strong"},{"start":65,"end":72,"type":"strong"}]},{"type":"paragraph","text":"Before getting into the data, we owe a thank you to our partners; GitHub (4th time partner) and Twilio (1st time partner), thank you for the support and guidance in planning, promotion, and production.","spans":[{"start":66,"end":72,"type":"strong"},{"start":96,"end":102,"type":"strong"}]},{"type":"paragraph","text":"Also, thank you to the maintainers of open source repositories on GitHub, for dedicating your blood (maybe?), sweat (likely), and tears (definitely!) to making this celebration possible for all the active participants around the world. We proclaim you the ‘Heroes of Hacktoberfest’ and have something to offer if you read to the end of this post.","spans":[{"start":236,"end":346,"type":"em"},{"start":329,"end":346,"type":"hyperlink","data":{"link_type":"Web","url":"#heroes"}}]},{"type":"heading3","text":"What happens when the world contributes","spans":[]},{"type":"paragraph","text":"Without further ado, here are some of the high-level stats from this year’s Hacktoberfest, all measuring the impact on open source you collectively created in the month of October.","spans":[]},{"type":"list-item","text":"Pull Requests from all participants: 412,324","spans":[{"start":37,"end":44,"type":"strong"}]},{"type":"list-item","text":"Participating repositories: 106,582","spans":[{"start":28,"end":35,"type":"strong"}]},{"type":"list-item","text":"Day with the most PRs: 20,300 on October 2nd","spans":[{"start":23,"end":29,"type":"strong"}]},{"type":"list-item","text":"Repo with most PRs: FreeCodeCamp with 12,090","spans":[{"start":38,"end":44,"type":"strong"}]},{"type":"list-item","text":"Participating Countries, based on addresses: 143","spans":[{"start":45,"end":48,"type":"strong"}]},{"type":"paragraph","text":"Behind those large-scale numbers are real stories of impact from various developer communities. Here are just a few that have already shared the impact and lessons learned: Gatsby.js, Kowainik, source{d}, weaveworks, and opsdroid.","spans":[{"start":173,"end":182,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.gatsbyjs.org/blog/2018-11-01-hacktoberfest-wrapup/?no-cache=1"}},{"start":184,"end":192,"type":"hyperlink","data":{"link_type":"Web","url":"https://kowainik.github.io/posts/2018-11-01-hacktoberfest-wrap-up"}},{"start":194,"end":203,"type":"hyperlink","data":{"link_type":"Web","url":"https://medium.com/sourcedtech/its-a-wrap-source-d-s-participation-in-hacktoberfest-9223814a6a49"}},{"start":205,"end":215,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.weave.works/blog/hacktoberfest-2018-its-a-wrap"}},{"start":221,"end":229,"type":"hyperlink","data":{"link_type":"Web","url":"https://medium.com/opsdroid/hacktoberfest-website-refresh-core-connectors-and-more-in-v0-13-9f3d3326427d"}}]},{"type":"paragraph","text":"Part of what makes Hacktoberfest possible are the vibrant developer communities that keep participating year after year. Some examples are Elastic, Changelog, Jenkins, Godot Engine, Home Assistant, OpenEBS, SendGrid, Hasura, Mattermost, dbatools, and many more.","spans":[{"start":139,"end":146,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.elastic.co/blog/hacktoberfest-2018-for-beats-and-logstash"}},{"start":148,"end":157,"type":"hyperlink","data":{"link_type":"Web","url":"https://changelog.com/news/get-your-hacktoberfest-shirt-without-writing-a-lick-of-code-2bzD"}},{"start":159,"end":166,"type":"hyperlink","data":{"link_type":"Web","url":"https://jenkins.io/blog/2018/10/01/hacktoberfest/"}},{"start":168,"end":180,"type":"hyperlink","data":{"link_type":"Web","url":"https://godotengine.org/article/become-godot-contributor-hacktoberfest-2018"}},{"start":182,"end":196,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.home-assistant.io/blog/2018/09/30/hacktoberfest/"}},{"start":198,"end":205,"type":"hyperlink","data":{"link_type":"Web","url":"https://blog.openebs.io/celebrate-hacktoberfest-2018-with-openebs-206daa1d653c"}},{"start":207,"end":215,"type":"hyperlink","data":{"link_type":"Web","url":"https://sendgrid.com/blog/hacktoberfest-2018-has-arrived/"}},{"start":217,"end":223,"type":"hyperlink","data":{"link_type":"Web","url":"https://blog.hasura.io/announcing-hacktoberfest-2018-with-hasura-621045dc9560"}},{"start":225,"end":235,"type":"hyperlink","data":{"link_type":"Web","url":"https://mattermost.com/blog/hacktoberfest-2018-is-here-its-a-chance-to-give-back-to-communities-you-love/"}},{"start":237,"end":245,"type":"hyperlink","data":{"link_type":"Web","url":"https://dbatools.io/hacktoberfest/"}}]},{"type":"heading3","text":"We’re all just getting started","spans":[]},{"type":"paragraph","text":"For many of you, Hacktoberfest was the first time getting involved in open source. This is a story we love to hear and we sometimes know where it leads. Here are participants’ stories who were beginners last year and have gone on to make strides in their work in 2018:","spans":[]},{"type":"paragraph","text":"Last year, people helped me create my first PR. I decided to pay back by helping people get theirs. I think that was my most valuable contribution this year. - Nikhil (India)","spans":[{"start":0,"end":174,"type":"em"}]},{"type":"paragraph","text":"I first began my Hacktoberfest quest last year. I was always inspired by people who contributed to open source software projects and aspired to one day be part of such a community. Hacktoberfest really got me started and I now am motivated to do this year wide whenever I have the time. - Constantinos (Greece)","spans":[{"start":0,"end":310,"type":"em"}]},{"type":"paragraph","text":"It's the second year participating, I keep encouraging people to participate as my first PR was in such an empowering and welcoming environment last year :) - Saul (Mexico)","spans":[{"start":0,"end":172,"type":"em"}]},{"type":"paragraph","text":"I made my first contribution to the open source last year under the influence of Hacktoberfest, that got me excited and I keep making uncountable contributions since then!! -Aminu (Nigeria)","spans":[{"start":0,"end":189,"type":"em"}]},{"type":"paragraph","text":"Last year was my first Hacktoberfest and I was a dev student trying to make my way into the field. For this year's Hacktoberfest, I'm an actual professional developer at a company!!!! -Tyler (USA)","spans":[{"start":0,"end":196,"type":"em"}]},{"type":"heading3","text":"Think globally, act locally","spans":[]},{"type":"paragraph","text":"From the beginning, Hacktoberfest has been inspired by developers who come together in real life to learn, build, and connect. Still, it is unexpected and absolutely remarkable to see the statistics from this year’s global events. For year 5 there were 267 events (compared with 119 events in 2017), organized in 50 countries! Here are the top 10 countries (and cities) by total events organized:","spans":[{"start":253,"end":256,"type":"strong"},{"start":253,"end":263,"type":"hyperlink","data":{"link_type":"Web","url":"https://do.co/hacktoberfest18events"}},{"start":279,"end":282,"type":"strong"},{"start":313,"end":315,"type":"strong"}]},{"type":"o-list-item","text":"India - 49 Events (Most in Bengaluru)","spans":[]},{"type":"o-list-item","text":"USA - 46 Events (Most in Atlanta)","spans":[]},{"type":"o-list-item","text":"Brazil - 20 Events (Most in Rio de Janeiro)","spans":[]},{"type":"o-list-item","text":"France - 15 Events (Most in Paris)","spans":[]},{"type":"o-list-item","text":"United Kingdom - 14 Events (Most in London)","spans":[]},{"type":"o-list-item","text":"Germany - 12 Events (Most in Berlin)","spans":[]},{"type":"o-list-item","text":"Canada - 11 Events (Most in Edmonton)","spans":[]},{"type":"o-list-item","text":"Mexico - 9 Events (Most in Guadalajara)","spans":[]},{"type":"o-list-item","text":"Poland - 8 Events (Most in Białystok)","spans":[]},{"type":"o-list-item","text":"Italy / Nigeria / Spain - 6 Events","spans":[]},{"type":"paragraph","text":"This would not have been possible without the impassioned community organizers around the world, who live and breathe events. You too have earned the title of ‘Heroes of Hacktoberfest!’ And a special thank you goes to Samantha Tse (DigitalOcean) and Joe Nash (GitHub) who created the invaluable Hacktoberfest Event Kit and supported organizers all month long.","spans":[{"start":126,"end":185,"type":"em"},{"start":218,"end":230,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/tse_samantha"}},{"start":250,"end":258,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/jna_sh"}},{"start":295,"end":318,"type":"hyperlink","data":{"link_type":"Web","url":"https://hacktoberfest.digitalocean.com/eventkit"}}]},{"type":"paragraph","text":"Here is some of the action from this year’s in-person gatherings:","spans":[]},{"type":"preformatted","text":"An awesome event for hacktoberfest! @ SIES Graduate School of Technology@github@digitalocean@hacktoberfest#Hacktoberfest2018#latepostpic.twitter.com/s6B9f7NyY6\n— Chinmay Chandak (@CCAtAlvis) October 31, 2018","spans":[{"start":72,"end":79,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/github?ref_src=twsrc%5Etfw"}},{"start":79,"end":92,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/digitalocean?ref_src=twsrc%5Etfw"}},{"start":92,"end":106,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/hacktoberfest?ref_src=twsrc%5Etfw"}},{"start":106,"end":124,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/hashtag/Hacktoberfest2018?src=hash&amp;ref_src=twsrc%5Etfw"}},{"start":124,"end":133,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/hashtag/latepost?src=hash&amp;ref_src=twsrc%5Etfw"}},{"start":133,"end":159,"type":"hyperlink","data":{"link_type":"Web","url":"https://t.co/s6B9f7NyY6"}},{"start":191,"end":207,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/CCAtAlvis/status/1057721180839464960?ref_src=twsrc%5Etfw"}}]},{"type":"preformatted","text":"Hosting the #Hacktoberfest in #Sarajevo second year running has been an amazing experience with some of my most favorite people in the world. It was also my first experience as a #Fireside Chat panel moderator! Lost of love and effort went into making the event awesome! pic.twitter.com/oDkPIh3wT4\n— Adnan Rahić (@adnanrahic) October 29, 2018","spans":[{"start":12,"end":26,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/hashtag/Hacktoberfest?src=hash&amp;ref_src=twsrc%5Etfw"}},{"start":30,"end":39,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/hashtag/Sarajevo?src=hash&amp;ref_src=twsrc%5Etfw"}},{"start":179,"end":188,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/hashtag/Fireside?src=hash&amp;ref_src=twsrc%5Etfw"}},{"start":271,"end":297,"type":"hyperlink","data":{"link_type":"Web","url":"https://t.co/oDkPIh3wT4"}},{"start":326,"end":342,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/adnanrahic/status/1056869064184868864?ref_src=twsrc%5Etfw"}}]},{"type":"preformatted","text":"We're at @hacktoberfest in Frankfurt. Awesome crowd, great food and open source. We're happy we can be a part of it! #hacktoberfest#Hacktoberfest2018#hacktoberfestffmpic.twitter.com/tYBiB79wwK\n— Rocketloop (@RocketloopHQ) October 12, 2018","spans":[{"start":9,"end":23,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/hacktoberfest?ref_src=twsrc%5Etfw"}},{"start":117,"end":131,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/hashtag/hacktoberfest?src=hash&amp;ref_src=twsrc%5Etfw"}},{"start":131,"end":149,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/hashtag/Hacktoberfest2018?src=hash&amp;ref_src=twsrc%5Etfw"}},{"start":149,"end":166,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/hashtag/hacktoberfestffm?src=hash&amp;ref_src=twsrc%5Etfw"}},{"start":166,"end":192,"type":"hyperlink","data":{"link_type":"Web","url":"https://t.co/tYBiB79wwK"}},{"start":222,"end":238,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/RocketloopHQ/status/1050800207565340674?ref_src=twsrc%5Etfw"}}]},{"type":"preformatted","text":"Badass @FlatironSchool team (and others!) At the @gdinyc#Hacktoberfest event today! I had so much fun actually coding with people in person. #WomenWhoCode#WomenInTechpic.twitter.com/m4YPRCKSQ3\n— Amanda🔮🌻👩🏻‍💻 (@amandacodes89) October 21, 2018","spans":[{"start":7,"end":22,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/FlatironSchool?ref_src=twsrc%5Etfw"}},{"start":49,"end":56,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/gdinyc?ref_src=twsrc%5Etfw"}},{"start":56,"end":70,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/hashtag/Hacktoberfest?src=hash&amp;ref_src=twsrc%5Etfw"}},{"start":141,"end":154,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/hashtag/WomenWhoCode?src=hash&amp;ref_src=twsrc%5Etfw"}},{"start":154,"end":166,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/hashtag/WomenInTech?src=hash&amp;ref_src=twsrc%5Etfw"}},{"start":166,"end":192,"type":"hyperlink","data":{"link_type":"Web","url":"https://t.co/m4YPRCKSQ3"}},{"start":225,"end":241,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/amandacodes89/status/1053835986399059968?ref_src=twsrc%5Etfw"}}]},{"type":"heading3","text":"Short term action, long term impact","spans":[]},{"type":"paragraph","text":"The values of Hacktoberfest remind us why we celebrate open source software. It is about creating an inclusive community that builds together to makes the world a better place. What happened during Hacktoberfest will have a lasting impact on people long after October 2018. This is why quality is preferred to quantity and why we’ll continue to improve the program to support this core value.","spans":[{"start":4,"end":27,"type":"hyperlink","data":{"link_type":"Web","url":"https://hacktoberfest.digitalocean.com/details/#values"}}]},{"type":"paragraph","text":"One thing that you can expect to see every year is the custom limited-edition t-shirts. Fun fact: the first batch of t-shirts we produced this year weighed 21,000 pounds (9,500 kg) and stickers weighed 650 pounds (295 kg). Better than stats, here's you, the participants who completed the challenge:","spans":[{"start":156,"end":162,"type":"strong"},{"start":171,"end":176,"type":"strong"},{"start":202,"end":205,"type":"strong"},{"start":214,"end":217,"type":"strong"}]},{"type":"preformatted","text":"My #hacktoberfest swag just got in yesterday! 🎉🎊 Thank you digitalocean for hosting the 5th Hacktoberfest Fest, and supporting thousands of open source projects 🔥👊🏻👨🏻‍💻 https://t.co/OqVdcFUyewpic.twitter.com/YH8l1gszR2\n— Aral Taşer (@araltasher) November 7, 2018","spans":[{"start":3,"end":17,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/hashtag/hacktoberfest?src=hash&amp;ref_src=twsrc%5Etfw"}},{"start":169,"end":192,"type":"hyperlink","data":{"link_type":"Web","url":"https://t.co/OqVdcFUyew"}},{"start":192,"end":218,"type":"hyperlink","data":{"link_type":"Web","url":"https://t.co/YH8l1gszR2"}},{"start":246,"end":262,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/araltasher/status/1060213995536347138?ref_src=twsrc%5Etfw"}}]},{"type":"preformatted","text":"Im never big on selfies, but I am really proud of this shirt #Hacktoberfest2018pic.twitter.com/k7rR68mynG\n— Cam Barts (@cam_barts) November 5, 2018","spans":[{"start":61,"end":79,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/hashtag/Hacktoberfest2018?src=hash&amp;ref_src=twsrc%5Etfw"}},{"start":79,"end":105,"type":"hyperlink","data":{"link_type":"Web","url":"https://t.co/k7rR68mynG"}},{"start":131,"end":147,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/cam_barts/status/1059448612445454338?ref_src=twsrc%5Etfw"}}]},{"type":"preformatted","text":"\"Woman laughing alone with #hacktoberfest shirt\" ... GOT IT! EXCITED! Thanks, @digitalocean 😍 #happysaturday#opensource#dev#keepcoding#gosteelerspic.twitter.com/KCA1sRESLe\n— Kristen Seversky (@KR1573N) November 3, 2018","spans":[{"start":27,"end":41,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/hashtag/hacktoberfest?src=hash&amp;ref_src=twsrc%5Etfw"}},{"start":78,"end":91,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/digitalocean?ref_src=twsrc%5Etfw"}},{"start":94,"end":108,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/hashtag/happysaturday?src=hash&amp;ref_src=twsrc%5Etfw"}},{"start":108,"end":119,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/hashtag/opensource?src=hash&amp;ref_src=twsrc%5Etfw"}},{"start":119,"end":123,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/hashtag/dev?src=hash&amp;ref_src=twsrc%5Etfw"}},{"start":123,"end":134,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/hashtag/keepcoding?src=hash&amp;ref_src=twsrc%5Etfw"}},{"start":134,"end":145,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/hashtag/gosteelers?src=hash&amp;ref_src=twsrc%5Etfw"}},{"start":145,"end":171,"type":"hyperlink","data":{"link_type":"Web","url":"https://t.co/KCA1sRESLe"}},{"start":202,"end":218,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/KR1573N/status/1058759919887884289?ref_src=twsrc%5Etfw"}}]},{"type":"preformatted","text":"V excited to have participated in #hacktoberfest! (but also to get a free t-shirt which is ofc the main reason anyone goes into tech) pic.twitter.com/98RaX2HxfB\n— Alexandra Fren (@alexandrafren) November 3, 2018","spans":[{"start":34,"end":48,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/hashtag/hacktoberfest?src=hash&amp;ref_src=twsrc%5Etfw"}},{"start":134,"end":160,"type":"hyperlink","data":{"link_type":"Web","url":"https://t.co/98RaX2HxfB"}},{"start":195,"end":211,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/alexandrafren/status/1058829276387180544?ref_src=twsrc%5Etfw"}}]},{"type":"paragraph","text":"As with previous years, spam is something not to be taken lightly when we are planning the program. This year we introduced a section for beginners, published quality standards, and included the program values in most of our communications to participants. Still we saw a hefty increase in Pull Requests labelled ‘Invalid.' From a manageable 1,861 in 2017 (.8% of total) to an unfortunate 11,083 (2.7%) in 2018.","spans":[{"start":342,"end":347,"type":"strong"},{"start":389,"end":395,"type":"strong"}]},{"type":"paragraph","text":"This means we’re going back to the drawing board and will be taking new measures to fight spam in 2019. Should we introduce reward tiers? Restrict PRs to personal projects? Or something else all together? Have ideas for minimizing invalid PRs? Leave a suggestion in the comments.","spans":[]},{"type":"heading3","text":"Closing remarks for 2018","spans":[]},{"type":"paragraph","text":"The great news is that Hacktoberfest is not really a month-long celebration of open source - it’s a culture and community that is always looking to give back. Through this ongoing work, we can accomplish almost anything we set our minds to. We’re looking forward to seeing what you have and will build all year long.","spans":[]},{"type":"paragraph","text":"SUPPORT: for all support-related inquiries about emails, usernames, addresses, and t-shirts, please contact our team as soon as possible: Hacktoberfest [at] DigitalOcean [dot] com.","spans":[]},{"type":"paragraph","text":"REMINDER: Keep a look-out for our first ever post-Hacktoberfest survey in which we’ll ask for your feedback on everything from the events, support, and t-shirts, set to go out in early December.","spans":[]},{"type":"paragraph","text":"Note to maintainers and meetup organizers: If you organized an event for +25 people or are a maintainer of a repo on GitHub that received 100 or more Pull Requests and has upwards of 500 stars, please reach out to us at the support email address above for a special thank you from us to you.","spans":[{"start":0,"end":41,"type":"em"}]}],"blog_post_date":"2018-11-09","tags":[{"tag1":{"tag":"Community","_linkType":"Link.document","_meta":{"uid":"community"}}}],"_meta":{"uid":"a-review-of-hacktoberfest-year-5"}}},{"node":{"author":{"_linkType":"Link.document","author_name":"Lisa Tagliaferri","author_image":{"dimensions":{"width":1490,"height":1608},"alt":"Lisa Tagliaferri","copyright":null,"url":"https://images.prismic.io/www-static/0d614f5dd9d938e9b5985c6817831c72ddf3c39a_lisa-tagliaferri-1.jpg?auto=compress,format"},"_meta":{"uid":"lisa_tagliaferri"}},"blog_header_image":{"dimensions":{"width":800,"height":600},"alt":null,"copyright":null,"url":"https://images.prismic.io/www-static/3cad75ad-88ff-4ce2-8f47-0affc3ead33c_donations_header-v1.png?auto=compress,format"},"blog_headline":[{"type":"heading1","text":"Documentation as an Open Source Practice","spans":[]}],"blog_post_content":[{"type":"paragraph","text":"As part of Hacktoberfest season, now is a good time to consider the ways in which we can make open source repositories welcoming spaces for developers and end users alike. A great way to facilitate contributions and grow a user base is through providing documentation and encouraging enhancements to your docs throughout the development cycle. ","spans":[{"start":11,"end":31,"type":"hyperlink","data":{"link_type":"Web","url":"https://blog.digitalocean.com/hacktoberfest-is-back-for-year-5/"}}]},{"type":"paragraph","text":"When thinking about documentation, work to incorporate it as a fundamental part of your open source practice: commit documentation early and often. ","spans":[]},{"type":"heading3","text":"Develop Documentation Like Code","spans":[]},{"type":"paragraph","text":"Along with your collaborators, document a feature prior to coding it, and document your code as you go along. This will make the codebase more coherent to developers who may work on your project down the line, as well as your future self!","spans":[]},{"type":"paragraph","text":"Be deliberate in the documentation you include within programming files and don’t overdo it. Code that is readable and follows a relevant style guide can often speak for itself with some comments along the way to provide context or explain a method that is not obvious. If you add too much documentation within a programming file, it may ironically make the code harder to read. Treat code like it will be read by other humans (because it will be) and comment conscientiously. ","spans":[]},{"type":"paragraph","text":"Just like everything else in your software project, docs need to be tested and maintained. When you modify code, get in the habit of editing comments and related documentation. Continue to iterate docs along with everything else, mitigating the need to do a documentation push as you approach a release. ","spans":[]},{"type":"heading3","text":"Include Community-Focused Documentation","spans":[]},{"type":"paragraph","text":"When you are ready to share software with the wider world through an open source repository, be sure to make your project approachable through including key files that provide a description of the software, guidelines and incentives for contributors, and tutorials for developers and end users alike. ","spans":[]},{"type":"paragraph","text":"Below are the files you’ll want to be sure to include, along with examples of these files from open source repositories available on GitHub. ","spans":[]},{"type":"list-item","text":"A ```[php]{`README.md`}``` file that describes the project and its goals; GitHub initializes this automatically but be sure to flesh it out","spans":[]},{"type":"list-item","text":"The EduBlocks README file is well-organized, with both visual screenshot instructions and manual instructions, and also features contributors in this central location","spans":[{"start":4,"end":20,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/JoshuaLowe1002/EduBlocks"}}]},{"type":"list-item","text":"A ```[php]{`CONTRIBUTING.md`}``` file to provide instructions for potential contributors — this helps developers understand the best way to collaborate on a project","spans":[]},{"type":"list-item","text":"“Contributing to JupyterLab” provides thorough guidelines and additional resources for contributors","spans":[{"start":1,"end":27,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/jupyterlab/jupyterlab/blob/master/CONTRIBUTING.md"}}]},{"type":"list-item","text":"Optionally, you may consider including a ```[php]{`CODE_STYLE.md`}``` file to give guidance around code best practices to use on a particular project, curl provides a good example","spans":[{"start":151,"end":179,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/curl/curl/blob/master/docs/CODE_STYLE.md"}}]},{"type":"list-item","text":"A ```[php]{`CODE_OF_CONDUCT.md`}``` file with a statement that reflects the project’s community values and sets expectations for both maintainers and contributors","spans":[]},{"type":"list-item","text":"The TensorFlow Code of Conduct, adapted from a few sources including the Contributor Covenant, details standards, responsibilities, and offers guidelines for conflict resolution as well as how to report violations","spans":[{"start":4,"end":30,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/tensorflow/tensorflow/blob/master/CODE_OF_CONDUCT.md"}},{"start":73,"end":93,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.contributor-covenant.org/version/1/4/code-of-conduct.html"}}]},{"type":"list-item","text":"A ```[php]{`CONTRIBUTORS.md`}``` or ```[php]{`AUTHORS.md`}``` file to recognize contributors, which will help you welcome and incentivize community engagement","spans":[]},{"type":"list-item","text":"Calagator offers a typical example of this type of file, with an alphabetical list of contributors and a link to this document from the repo’s ```[php]{`README.me`}``` file ","spans":[{"start":0,"end":9,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/calagator/calagator/blob/master/CONTRIBUTORS.md"}}]},{"type":"paragraph","text":"To support end users, consider including tutorial-style documentation that offers guidance on how to use the software. When writing tutorials, think about who your target audience is, and who your actual audience may be. While you are an expert on your project, approach writing your documentation as though you are a beginner who is using your software for the first time. If you have the resources, consider doing some user testing to watch new users interact with the software so that you can be sure to provide the support that they need to use your software in a meaningful way. ","spans":[]},{"type":"heading4","text":"Encourage Documentation Contributions","spans":[]},{"type":"paragraph","text":"One of the most important contributions someone can make to an open source repository is a documentation update. For emerging developers, a pull request that improves existing docs based on their experience using a tool provides an entry to get involved in open source. Creating an environment where people are equally acknowledged for code and documentation contributions can go a long way to building a community around your project. ","spans":[]},{"type":"paragraph","text":"It can be challenging for people who are close to the code to fully understand the needs of new contributors or end users. By encouraging the contributions of diverse voices to your documentation, your project will in turn become more useful for more people, positioning it to reach a wider audience.","spans":[]},{"type":"paragraph","text":"Striving to make documentation inclusive and accessible and seeking out the perspectives of others can also support bringing more developers and end users to your software. Some things you can do to foster engagement include: ","spans":[]},{"type":"list-item","text":"Use language that is welcoming and unbiased","spans":[]},{"type":"list-item","text":"Offer natural language translations to better serve distributed communities","spans":[]},{"type":"list-item","text":"Determine the best reading level for your audience","spans":[]},{"type":"list-item","text":"Consider alternatives to text-based tutorials, incorporating audio and visual components may help others understand better","spans":[]},{"type":"list-item","text":"Optimize documentation for screen readers","spans":[]},{"type":"list-item","text":"Ensure that web-based tutorials are A11Y compliant","spans":[{"start":36,"end":40,"type":"hyperlink","data":{"link_type":"Web","url":"https://a11yproject.com/"}}]},{"type":"paragraph","text":"There are many things you can do to make your project’s docs more inclusive across communities. Welcoming others as collaborators on documentation supports your efforts in meeting the needs of more people so that you can scale your project. ","spans":[]},{"type":"paragraph","text":"To learn more about technical documentation, the Write the Docs community has comprehensive resources and offers opportunities to connect with others who are working on docs and open source. You can read more about contributing to open source and maintaining repositories by checking out our Introduction to Open Source tutorial series. ","spans":[{"start":49,"end":63,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.writethedocs.org/"}},{"start":292,"end":319,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorial_series/an-introduction-to-open-source"}}]},{"type":"paragraph","text":"Hacktoberfest is on now, and runs until October 31.","spans":[{"start":0,"end":23,"type":"hyperlink","data":{"link_type":"Web","url":"https://hacktoberfest.digitalocean.com/"}}]},{"type":"paragraph","text":"Lisa Tagliaferri is on the DigitalOcean Community team. You can find over 2,000 tutorials about DevOps, Development, and Open Source on our Community site. Follow Lisa on Twitter @lisaironcutter.","spans":[{"start":0,"end":195,"type":"em"},{"start":140,"end":154,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/"}},{"start":179,"end":194,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/lisaironcutter?lang=en"}}]}],"blog_post_date":"2018-10-18","tags":[{"tag1":{"tag":"Community","_linkType":"Link.document","_meta":{"uid":"community"}}}],"_meta":{"uid":"documentation-as-an-open-source-practice"}}},{"node":{"author":{"_linkType":"Link.document","author_name":"DigitalOcean","author_image":{"dimensions":{"width":600,"height":600},"alt":"Sammy avatar","copyright":null,"url":"https://images.prismic.io/www-static/a10e3c2eb15b74ee43f872be3044313423b1c9a9_sammy_avatar.png?auto=compress,format"},"_meta":{"uid":"digitalocean"}},"blog_header_image":{"dimensions":{"width":800,"height":600},"alt":"People illustrations connected by github cat illustration in the middle by lines","copyright":null,"url":"https://images.prismic.io/www-static/302d9b87195e6096e0f9398c4c1250528122862c_web-hosting-timeline-github.png?auto=compress,format"},"blog_headline":[{"type":"heading1","text":"Blasting Off to GitHub Universe","spans":[]}],"blog_post_content":[{"type":"paragraph","text":"It's that magical time of the year again: GitHub Universe, being held October 16-17, 2018, in San Francisco!","spans":[{"start":42,"end":57,"type":"hyperlink","data":{"link_type":"Web","url":"https://githubuniverse.com"}}]},{"type":"paragraph","text":"Come check out our area, chat with our DevRel team, and take a picture with Sammy. For every attendee that shares their photo on Twitter or Instagram using the hashtag #DigitalOceanLovesOS, we’ll donate $5 to the Electronic Frontier Foundation (EFF), up to a maximum of $5,000. It’s just another way we can support the open source community, especially during this Hacktoberfest month!","spans":[{"start":213,"end":249,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.eff.org/"}},{"start":307,"end":340,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/open-source/"}},{"start":360,"end":384,"type":"hyperlink","data":{"link_type":"Web","url":"https://blog.digitalocean.com/hacktoberfest-is-back-for-year-5/"}}]},{"type":"paragraph","text":"Also make sure to check out Sneha's talk on October 17 at 3:20 PM, \"Observability pre-release: using Prometheus to test and fix new software.\"","spans":[]},{"type":"paragraph","text":"The pillars of observability have long been accepted as key components of any microservice-in-production. But what about those new products—those new features—that have yet to be released? Properly instrumenting and leveraging metrics at this stage is perhaps even more crucial. When a product is yet to be released, identifying and addressing early bugs is critical.","spans":[{"start":0,"end":367,"type":"em"}]},{"type":"paragraph","text":"See how the team at DigitalOcean leveraged Prometheus to properly instrument and test features within their software-defined networking pillar. This session will highlight instrumentation, key visualizations, and takeaways from their experience. You’ll also hear about areas for improvement and find out how to use these learnings for your own releases.","spans":[{"start":0,"end":353,"type":"em"}]}],"blog_post_date":"2018-10-15","tags":[{"tag1":{"tag":"Community","_linkType":"Link.document","_meta":{"uid":"community"}}}],"_meta":{"uid":"blasting-off-to-github-universe"}}},{"node":{"author":{"_linkType":"Link.document","author_name":"Daniel Zaltsman","author_image":{"dimensions":{"width":188,"height":188},"alt":"Daniel Zaltsman","copyright":null,"url":"https://images.prismic.io/www-static/663d428f56c46eeb165c811add8f1f60402aa451_daniel_zaltsman-c47f4847.png?auto=compress,format"},"_meta":{"uid":"daniel_zaltsman"}},"blog_header_image":{"dimensions":{"width":2134,"height":1068},"alt":"Hacktoberfest 2018 illustration","copyright":null,"url":"https://images.prismic.io/www-static/87792fe220343bfca7b344eee8dd68a30ab35293_hacktoberfest_2018_twitter_facebook1_2134x1068.png?auto=compress,format"},"blog_headline":[{"type":"heading1","text":"Hacktoberfest is Back for Year 5","spans":[]}],"blog_post_content":[{"type":"paragraph","text":"DigitalOcean started Hacktoberfest with a simple mission in mind: to give back to the open source community that had given us and many other companies so much.  We combined our customers’ love of our swag with a challenge: contribute meaningfully to projects and earn a limited edition t-shirt. Five years later, more than 100,000 developers from 120 countries have participated, contributing nearly 400,000 pull requests to almost 100,000 repositories. We’re back for another year and officially welcome you to our fifth annual celebration of open source in partnership with GitHub and Twilio!","spans":[{"start":323,"end":341,"type":"strong"},{"start":347,"end":360,"type":"strong"},{"start":400,"end":421,"type":"strong"},{"start":432,"end":452,"type":"strong"}]},{"type":"heading4","text":"Highlights for 2018","spans":[]},{"type":"paragraph","text":"The purpose of this celebration and much of the experience remains the same. However, Hacktoberfest 2018 has a handful of new surprises as well. There are some new rules and details that you will want to read before starting. We added more resources for beginners to get involved. We have also introduced 'Hacktoberfest Values' this year, which we hope will bring all of us even closer together around the shared goal of giving back meaningfully. A new partner joins us. And we’ve also introduced a cap on t-shirts which we’ll go into more detail about in this post. Here’s what you need to know about this year’s celebration.","spans":[{"start":305,"end":327,"type":"hyperlink","data":{"link_type":"Web","url":"https://hacktoberfest.digitalocean.com/details#values"}}]},{"type":"heading4","text":"Rules for Participation","spans":[]},{"type":"paragraph","text":"It’s hard to believe it’s already year five of Hacktoberfest! To celebrate our fifth anniversary and add a bit more focus on contributions, we are upping the ante. To complete the challenge this year, participants must make five pull requests (PRs) between October 1–31 in any timezone to any public repo on GitHub. As in previous years, the PR(s) reported by maintainers as spam or that are automated will be marked as invalid and won't count towards the shirt. Visit the details page for rules and participation info.","spans":[{"start":224,"end":242,"type":"strong"},{"start":463,"end":485,"type":"hyperlink","data":{"link_type":"Web","url":"https://hacktoberfest.digitalocean.com/details"}}]},{"type":"heading4","text":"Delivering Happiness T-Shirts","spans":[]},{"type":"paragraph","text":"In 2017, in partnership with custom apparel company Kotis Design, we shipped approximately 32,000 shirts to 120 countries. Manufacturing t-shirts and sending them to all of you makes up the main cost of this program. This year we decided to make an even bigger investment so more people than ever can participate in Hacktoberfest 2018 and be rewarded for your hard-earned effort. We’re excited to announce that this year, the first 50,000 of you who complete the challenge will earn a shirt.","spans":[{"start":52,"end":64,"type":"hyperlink","data":{"link_type":"Web","url":"https://kotisdesign.com/"}}]},{"type":"image","url":"https://images.prismic.io/www-static/e4d6154796b70e1763765dc263e8c0daf8d1d321_chart.png?auto=compress,format","alt":null,"copyright":null,"dimensions":{"width":1540,"height":840}},{"type":"heading4","text":"Community Makes it Great","spans":[]},{"type":"paragraph","text":"Over the past two years, the  event kit developed as a partnership between Samantha Tse (DigitalOcean) and Joe Nash (GitHub), has led to major offline community growth. We went from 25 meetups in 2016 to 125 in 2017, and this year there are already  80+ events organized on the first day of the month! Last year we also saw more than 15,000 mentions of the #Hacktoberfest hashtag on Twitter alone, providing you all with a rich resource for connecting online. We’re also extremely impressed with companies who dedicate their teams’ time to participating. Shining examples of this are US-based SendGrid (blog post) and India-based OpenEBS (blog post). We share all this with you as a reminder that this program is only possible with your effort.","spans":[{"start":29,"end":39,"type":"hyperlink","data":{"link_type":"Web","url":"https://hacktoberfest.digitalocean.com/eventkit"}},{"start":75,"end":87,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/samantha_tse"}},{"start":107,"end":115,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/jna_sh"}},{"start":249,"end":270,"type":"hyperlink","data":{"link_type":"Web","url":"http://do.co/hacktoberfest18events"}},{"start":357,"end":379,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/search?f=tweets&amp;vertical=default&amp;q=%23hacktoberfest&amp;src=typd&amp;lang=en"}},{"start":603,"end":612,"type":"hyperlink","data":{"link_type":"Web","url":"https://sendgrid.com/blog/hacktoberfest-2018-hack-on-sendgrid-open-source-projects/"}},{"start":639,"end":648,"type":"hyperlink","data":{"link_type":"Web","url":"https://blog.openebs.io/celebrate-hacktoberfest-2018-with-openebs-206daa1d653c"}}]},{"type":"paragraph","text":"P.S. for you long-time participants - If you are a community member that has shirts from all four previous years, please leave a comment to receive a special surprise!","spans":[{"start":0,"end":167,"type":"em"}]},{"type":"heading4","text":"Closing Remarks","spans":[]},{"type":"paragraph","text":"If you have questions about the program, please review the details page and FAQ we’ve created just for you. We’d also like to thank our partners in this program: GitHub, who we’ve been working with for three years, and our new partner, Twilio, who we’re thrilled to have joining us this year! That’s it from us. We’re excited to see what you co-create in 2018 and the impact it has on the technologies we all use and love.","spans":[{"start":59,"end":71,"type":"hyperlink","data":{"link_type":"Web","url":"https://hacktoberfest.digitalocean.com/details"}},{"start":76,"end":79,"type":"hyperlink","data":{"link_type":"Web","url":"https://hacktoberfest.digitalocean.com/faq"}}]},{"type":"paragraph","text":"Thank you for participating. Have a Happy Hacktoberfest!","spans":[]},{"type":"paragraph","text":"For more info about Hacktoberfest 2018, tune into this special edition episode of the Chagelog podcast - The Changelog 317: #Hacktoberfest isn’t just about a free shirt – You can listen on Changelog.com or directly below. Thanks!","spans":[{"start":105,"end":168,"type":"hyperlink","data":{"link_type":"Web","url":"https://changelog.com/podcast/317"}},{"start":189,"end":202,"type":"hyperlink","data":{"link_type":"Web","url":"https://changelog.com/podcast/317"}}]}],"blog_post_date":"2018-10-01","tags":[{"tag1":{"tag":"Community","_linkType":"Link.document","_meta":{"uid":"community"}}}],"_meta":{"uid":"hacktoberfest-is-back-for-year-5"}}},{"node":{"author":{"_linkType":"Link.document","author_name":"Kamal Nasser","author_image":{"dimensions":{"width":1008,"height":1008},"alt":null,"copyright":null,"url":"https://images.prismic.io/www-static/e2285fcfaf32ce7ec26329fe7e416ae896fbf991_portrait_2k18_bw_smallres.jpg?auto=compress,format"},"_meta":{"uid":"kamal-nasser"}},"blog_header_image":{"dimensions":{"width":784,"height":418},"alt":"Fully-automated Git-based Static Website","copyright":null,"url":"https://images.prismic.io/www-static/0aaec289-b9b2-4d3f-8dfc-0d1d2bdb349a_www_under_5_blog.png?auto=compress,format"},"blog_headline":[{"type":"heading1","text":"Deploying a Fully-automated Git-based Static Website in Under 5 Minutes","spans":[]}],"blog_post_content":[{"type":"paragraph","text":"Sometimes you simply want to get a static website up and running as quickly as possible, whether it be the actual website, a placeholder, or a basic landing page. Recently I started using Caddy, a modern web-server focused on simplicity and security. It includes native support for Git and Let's Encrypt thanks to its plugin-based architecture. ","spans":[]},{"type":"paragraph","text":"RELATED: Implementing HTTPS for Chrome Users","spans":[{"start":0,"end":44,"type":"strong"},{"start":9,"end":44,"type":"hyperlink","data":{"link_type":"Web","url":"https://blog.digitalocean.com/implementing-https-for-chrome-users/"}}]},{"type":"paragraph","text":"I love how easy-to-use Caddy is, and I wanted to share a tutorial about how we can deploy a static website synced with a Git repository in under 5 minutes—all on a Droplet that spins up in 55 seconds.","spans":[]},{"type":"heading4","text":"Prerequisites","spans":[]},{"type":"o-list-item","text":"An Ubuntu 16.04 server configured according to our Initial Server Setup guide. While the goal is to get a website up quickly, we don't want to skimp on security. The guide will set up a secure environment for Caddy. Skip step 7 of the guide as we will be using a Cloud Firewall instead of a software firewall installed on the Droplet.","spans":[{"start":51,"end":77,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/initial-server-setup-with-ubuntu-16-04"}},{"start":263,"end":277,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/products/cloud-firewalls/"}}]},{"type":"o-list-item","text":"A domain name you own, to be used for the website.","spans":[]},{"type":"heading4","text":"Step 1 — Install Caddy","spans":[]},{"type":"paragraph","text":"Caddy provides pre-built binaries on its website. Download Caddy on your Droplet:","spans":[{"start":37,"end":48,"type":"hyperlink","data":{"link_type":"Web","url":"https://caddyserver.com/download"}}]},{"type":"preformatted","text":"```[php]{`","spans":[]},{"type":"paragraph","text":"    wget -O caddy.tar.gz \"https://caddyserver.com/download/linux/amd64?plugins=http.git&license=personal\"  ","spans":[]},{"type":"preformatted","text":"`}```","spans":[]},{"type":"paragraph","text":"This command will download a Caddy binary with the following settings:","spans":[]},{"type":"paragraph","text":"Platform: Linux 64-bit ","spans":[]},{"type":"paragraph","text":"Plugins: http.git ","spans":[]},{"type":"paragraph","text":"License: Personal","spans":[]},{"type":"paragraph","text":"Keep in mind that the personal license is available for non-commercial use only.","spans":[]},{"type":"paragraph","text":"Extract the downloaded archive into a new directory and `cd` into it:","spans":[]},{"type":"preformatted","text":"```[php]{`","spans":[]},{"type":"paragraph","text":"    mkdir caddy  ","spans":[]},{"type":"paragraph","text":"    tar vxf caddy.tar.gz -C caddy  ","spans":[]},{"type":"paragraph","text":"    cd caddy  ","spans":[]},{"type":"preformatted","text":"`}```","spans":[]},{"type":"paragraph","text":"The archive contains the Caddy binary and a Systemd service file. We will use both in this guide. First, install the binary:","spans":[]},{"type":"preformatted","text":"```[php]{`","spans":[]},{"type":"paragraph","text":"    sudo cp caddy /usr/local/bin  ","spans":[]},{"type":"paragraph","text":"    sudo chown root:root /usr/local/bin/caddy  ","spans":[]},{"type":"paragraph","text":"    sudo chmod 755 /usr/local/bin/caddy  ","spans":[]},{"type":"preformatted","text":"`}```","spans":[]},{"type":"paragraph","text":"Because Caddy will serve as our front-facing web server, it will need to be able to listen on ports 80 and 443. Linux requires binaries to be run as root in order to listen on any port under 1024. It is however possible to allow specific binaries to do so without full root privileges:","spans":[]},{"type":"preformatted","text":"```[php]{`","spans":[]},{"type":"paragraph","text":"    sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/caddy  ","spans":[]},{"type":"preformatted","text":"`}```","spans":[]},{"type":"paragraph","text":"Then, create Caddy's configuration directories and set proper permissions:","spans":[]},{"type":"preformatted","text":"```[php]{`","spans":[]},{"type":"paragraph","text":"    sudo mkdir /etc/caddy  ","spans":[]},{"type":"paragraph","text":"    sudo chown -R root:www-data /etc/caddy  ","spans":[]},{"type":"paragraph","text":"    sudo mkdir /etc/ssl/caddy  ","spans":[]},{"type":"paragraph","text":"    sudo chown -R www-data:root /etc/ssl/caddy  ","spans":[]},{"type":"paragraph","text":"    sudo chmod 0770 /etc/ssl/caddy  ","spans":[]},{"type":"preformatted","text":"`}```","spans":[]},{"type":"paragraph","text":"Finally, install the Systemd service file:","spans":[]},{"type":"preformatted","text":"```[php]{`","spans":[]},{"type":"paragraph","text":"    sudo cp init/linux-systemd/caddy.service /etc/systemd/system/  ","spans":[]},{"type":"paragraph","text":"    sudo chown root:root /etc/systemd/system/caddy.service  ","spans":[]},{"type":"paragraph","text":"    sudo chmod 644 /etc/systemd/system/caddy.service  ","spans":[]},{"type":"paragraph","text":"    sudo systemctl daemon-reload  ","spans":[]},{"type":"preformatted","text":"`}```","spans":[]},{"type":"heading4","text":"Step 2 — Configure DNS","spans":[]},{"type":"paragraph","text":"Before configuring and starting Caddy we want to set up DNS so that Caddy is able to issue an SSL certificate via Let's Encrypt.","spans":[]},{"type":"paragraph","text":"Add your domain name in the Domains page. We will create two DNS records pointing to the Droplet: one for IPv4 and one for IPv6.","spans":[{"start":28,"end":35,"type":"hyperlink","data":{"link_type":"Web","url":"https://cloud.digitalocean.com/networking/domains"}}]},{"type":"paragraph","text":"The first will be of type A. In the hostname field, enter `@`. Select your Droplet in the Will Direct To field and add the record. The second record will have the same settings but with type AAAA.","spans":[]},{"type":"heading4","text":"Step 3 — Configure Caddy","spans":[]},{"type":"paragraph","text":"For the purposes of this guide, we will use the following example website: https://github.com/kamaln7/basic-static-website","spans":[{"start":75,"end":122,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/kamaln7/basic-static-website"}}]},{"type":"paragraph","text":"Caddy's configuration file will be located in `/etc/caddy/Caddyfile`. Open the file in a text editor (nano, vim, etc.) and enter the following:  ","spans":[]},{"type":"preformatted","text":"```[php]{`","spans":[]},{"type":"paragraph","text":"    example.com {  ","spans":[]},{"type":"paragraph","text":"        tls you@example.com","spans":[]},{"type":"paragraph","text":"        internal /.git","spans":[]},{"type":"paragraph","text":"        git https://github.com/kamaln7/basic-static-website.git {","spans":[]},{"type":"paragraph","text":"            interval 300","spans":[]},{"type":"paragraph","text":"        }","spans":[]},{"type":"paragraph","text":"        gzip","spans":[]},{"type":"paragraph","text":"        redir 301 {","spans":[]},{"type":"paragraph","text":"            if {scheme} is http","spans":[]},{"type":"paragraph","text":"            /  https://{host}{uri}","spans":[]},{"type":"paragraph","text":"        }","spans":[]},{"type":"paragraph","text":"    }","spans":[]},{"type":"preformatted","text":"`}```","spans":[]},{"type":"paragraph","text":"Replace `example.com` with your domain name and `you@example.com` with your email address. This email address will be used to issue a Let's Encrypt certificate for your domain so make sure to enter a valid one that you have access to.","spans":[]},{"type":"paragraph","text":"This configures basic sane defaults: gzip compression will be used when suitable and all HTTP traffic will be redirected to HTTPS.","spans":[]},{"type":"paragraph","text":"The main piece of this configuration is the `git` block. This will configure Caddy to use the Git repository’s contents as the website's files, checking for updates every 5 minutes.","spans":[]},{"type":"heading4","text":"Step 4 — Start Caddy","spans":[]},{"type":"paragraph","text":"Start Caddy, and enable it to start on boot:","spans":[]},{"type":"preformatted","text":"```[php]{`","spans":[]},{"type":"paragraph","text":"    sudo systemctl start caddy  ","spans":[]},{"type":"paragraph","text":"    sudo systemctl enable caddy","spans":[]},{"type":"preformatted","text":"`}```","spans":[]},{"type":"paragraph","text":"It might take a few seconds for Caddy to receive the certificate from Let's Encrypt and clone your repository, but you should now be able to browse to your domain name and see your website.","spans":[]},{"type":"paragraph","text":"Now, any changes you make in your Git repository will be automatically applied.","spans":[]},{"type":"heading4","text":"Step 5 — Configure a Firewall","spans":[]},{"type":"paragraph","text":"DigitalOcean Cloud Firewalls make it very easy to configure a secure firewall. Browse to the Firewalls page and click on the Create Firewall button. If you already have one or more Firewalls on your account, you can access the Create Firewall page through the Create menu at the top of the page.","spans":[{"start":93,"end":107,"type":"hyperlink","data":{"link_type":"Web","url":"https://cloud.digitalocean.com/networking/firewalls"}}]},{"type":"paragraph","text":"For the inbound rules, we will allow SSH, HTTP, and HTTPS traffic. Keep the outbound rules as is. Select your Droplet and create the firewall. ","spans":[]},{"type":"paragraph","text":"For further instructions on Cloud Firewalls, see our tutorial How To Create Your First DigitalOcean Cloud Firewall.","spans":[{"start":62,"end":114,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials/how-to-create-your-first-digitalocean-cloud-firewall"}}]},{"type":"heading4","text":"Optional: Step 6 — Enable Instant Deployments using Webhooks","spans":[]},{"type":"paragraph","text":"Caddy will check the Git repository for changes every five minutes by default. While you can set the interval to a lower value, a better solution would be to configure GitHub to push any changes to Caddy instead. This will allow for near-instant updates.","spans":[]},{"type":"paragraph","text":"Caddy makes this process very easy as well. The webhook will require a secret—you can use anything you want. The `uuidgen` program is a convenient tool that allows you to easily generate a random secure string. Simply run `uuidgen` and copy its output.","spans":[]},{"type":"paragraph","text":"Edit `Caddyfile` and add the following line inside the Git block, replacing secret with your secret:","spans":[]},{"type":"preformatted","text":"```[php]{`","spans":[]},{"type":"paragraph","text":"    hook /github_hook secret  ","spans":[]},{"type":"preformatted","text":"`}```","spans":[]},{"type":"paragraph","text":"Restart Caddy to apply the changes:","spans":[]},{"type":"preformatted","text":"```[php]{`","spans":[]},{"type":"paragraph","text":"    sudo systemctl restart caddy  ","spans":[]},{"type":"preformatted","text":"`}```","spans":[]},{"type":"paragraph","text":"Then, configure Github to use the new webhook endpoint: browse to your repository's settings page and click on Webhooks. Add a new webhook and set the Payload URL to `https://domain.com/github_hook`. Set the the Content type to `application/json` and enter your secret and click on Add Webhook.","spans":[]},{"type":"paragraph","text":"Now, whenever you push a change to your Git repository, it will be reflected on your website in seconds. For instance, if you are using the example website mentioned above, go ahead and change the highlight color to blue by replacing `b--gold` with `b--blue`. Commit the updated file and reload the page!","spans":[{"start":140,"end":155,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/kamaln7/basic-static-website"}}]},{"type":"heading4","text":"Conclusion","spans":[]},{"type":"paragraph","text":"By following this guide, you will have deployed a fully-automated low-maintenance website using modern technologies such as HTTP/2 and Let's Encrypt. Caddy is a versatile web server and supports many great features such as on-the-fly Markdown rendering. You can find a list of plugins and features on the Features page on its website. Browse through the documentation to see what features you might want to enable and how to do so.","spans":[{"start":305,"end":318,"type":"hyperlink","data":{"link_type":"Web","url":"https://caddyserver.com/features"}},{"start":350,"end":367,"type":"hyperlink","data":{"link_type":"Web","url":"https://caddyserver.com/docs"}}]},{"type":"paragraph","text":"Kamal Nasser is a Developer Advocate at DigitalOcean. He is also a Computer Science student with a passion for software engineering and avocados. You can find him on Twitter @kamaln7.","spans":[{"start":0,"end":183,"type":"em"},{"start":174,"end":182,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/kamaln7"}}]}],"blog_post_date":"2018-08-08","tags":[{"tag1":{"tag":"Community","_linkType":"Link.document","_meta":{"uid":"community"}}}],"_meta":{"uid":"deploying-a-fully-automated-git-based-static-website-in-under-5-minutes"}}},{"node":{"author":{"_linkType":"Link.document","author_name":"Karan M.V.","author_image":null,"_meta":{"uid":"karan_m_v_"}},"blog_header_image":{"dimensions":{"width":784,"height":418},"alt":"boxes illustration","copyright":null,"url":"https://images.prismic.io/www-static/4b72fc5ef5c399d1a62f17fd45900df3a679f7aa_do_webinars_blog.png?auto=compress,format"},"blog_headline":[{"type":"heading1","text":"Engaging Developer Communities through Webinars (Part 1)","spans":[]}],"blog_post_content":[{"type":"paragraph","text":"As as developer-first company, we are always looking for meaningful channels to engage with the developer community. Our Community site contains over 1,900 tutorials on various DevOps and system administrator topics, and our Meetups host a community of 40,000+ members across 73 groups in 33 countries where members share resources, learn, and have discussions about technical topics.","spans":[{"start":150,"end":165,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/tutorials"}},{"start":225,"end":232,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.meetup.com/pro/digitalocean"}}]},{"type":"paragraph","text":"We started organizing webinars as a way to combine the best of both worlds: the learning experience available through our online tutorials and the interactivity and camaraderie facilitated through our in-person meetups. Since our first webinar in September 2016, we have organized more than 30 webinars on a wide range of themes, including containers, orchestration, CI/CD, configuration management, and big data, with developers from around the world tuning in to our content.","spans":[]},{"type":"paragraph","text":"We’ve learned a lot over the past two years and want to share how community organizers can create webinars to engage further with their communities.","spans":[]},{"type":"heading2","text":"1. Choosing the right webinar tool","spans":[]},{"type":"paragraph","text":"The virtual nature of webinars is a prime advantage compared to in-person events. Attendees can join from anywhere in the world, providing them greater flexibility and accessibility. Without the limits of physical capacity, webinars allow for greater attendance.","spans":[]},{"type":"paragraph","text":"Organizers of in-person events have an arsenal of techniques for audience engagement, such as Q&As, live quizzes and social media contests. To retain some of that flavor online, you must carefully choose the tool for delivering a webinar. Commonly-used tools include Google Hangouts, Zoom, GoToWebinar, and On24.","spans":[]},{"type":"paragraph","text":"We use GoToWebinar for many of our webinars. To help attendees focus on the demos and technical content that a speaker is sharing, we primarily do screencasts. This, coupled with other in-session engagement features such as live polls, helps us gauge audience interest and customize our content on-the-fly.","spans":[]},{"type":"paragraph","text":"Tip: Many of the webinar and engagement tools often have associated costs. Evaluating the tools in the context of your goals and budget will help you deliver more value to your community.","spans":[]},{"type":"heading2","text":"2. Getting the right equipment","spans":[]},{"type":"paragraph","text":"If a webinar session is being recorded for later viewing, it is important to consider factors that would produce a high-quality video output suitable to your audience. Some of these are:","spans":[]},{"type":"paragraph","text":"Video Resolution","spans":[{"start":0,"end":16,"type":"strong"}]},{"type":"paragraph","text":"Ensure the tool you choose supports recording in HD resolution or higher. In some tools, this depends on the resolution of the speaker’s display or webcam.","spans":[]},{"type":"paragraph","text":"Audio Quality","spans":[{"start":0,"end":13,"type":"strong"}]},{"type":"paragraph","text":"Many professional webinar speakers and organizers invest in a professional microphone setup for a high-quality audio recording. Whenever possible, do a trial-run with your speakers to test their microphones, the ambient noise, and their internet connection to ensure a better experience for your attendees.","spans":[]},{"type":"paragraph","text":"Post-Processing","spans":[{"start":0,"end":15,"type":"strong"}]},{"type":"paragraph","text":"Like any other video, many webinar sessions require post-processing such as video editing, animation overlays, and audio corrections, which will help deliver a better video output. The time taken for this process can be reduced by checking audio and video quality before the session, ensuring relevant animations/diagrams/graphics are created beforehand, and other post-processing factors such as frame-rates are considered.","spans":[]},{"type":"heading2","text":"3. Targeting attendees globally","spans":[]},{"type":"paragraph","text":"Due to the virtual nature of webinars, developers from all around the world can attend or engage with your sessions. While targeting to engage with developers globally, there are a few key things to keep in mind:","spans":[]},{"type":"paragraph","text":"Content","spans":[{"start":0,"end":7,"type":"strong"}]},{"type":"paragraph","text":"The content must resonate with your global audience and not be restricted to a certain geography. For example, a webinar on best practices for hosting developer events in the U.S. might not relate to community organizers in India.","spans":[]},{"type":"paragraph","text":"Speaker/s","spans":[{"start":0,"end":9,"type":"strong"}]},{"type":"paragraph","text":"The speaker for the sessions should also be relevant to the global audience in terms of their vocabulary, language, and speaking style. The content delivered by the speaker should be understood by both native and non-native English speakers.","spans":[]},{"type":"paragraph","text":"Time","spans":[{"start":0,"end":4,"type":"strong"}]},{"type":"paragraph","text":"It is essential that the time of the live session is considerate of the global audience. The cultural and social factors of the target geographies come into play here while deciding the appropriate time for your session, such as weekends vs. weekdays, evenings vs. mornings, and length. Based on your target audience, you can identify a ‘sweet-spot’ that would be suitable for a majority of your target audience. An alternative to this would be to host multiple sessions in different time zones that would be convenient to the respective regions.","spans":[]},{"type":"paragraph","text":"The content of our webinars is technology-oriented and geography-agnostic, which draws thousands of developers from all over the world. The speakers delivering this content are well-known subject matter experts who have experience in delivering their content to international audiences.","spans":[]},{"type":"paragraph","text":"Tip: Having an idea of the geographies that you wish to target will help you build better engagement with developers across the world.","spans":[]},{"type":"heading2","text":"4. Creating a learning path","spans":[]},{"type":"paragraph","text":"A major reason for your audience to attend webinars is to learn by the various means available through a live session. Not every attendee might be fully equipped to assimilate the content in terms of their understanding and skill levels. While some might be experts on the content and expecting answers to specific questions, others might be beginners looking to understand the basics.","spans":[]},{"type":"paragraph","text":"Creating a learning path will help attendees evaluate their understanding and self-select their learning path by attending all or a specific set of sessions within a published content curriculum. The learning path can consist of multiple webinar sessions along with various types of accompanying supplementary resources.","spans":[]},{"type":"paragraph","text":"In our webinar series on containers and microservices, our learning path consisted of six webinar sessions with accompanying DigitalOcean tutorials and quizzes for self-evaluation.","spans":[{"start":7,"end":53,"type":"hyperlink","data":{"link_type":"Web","url":"https://go.digitalocean.com/containers-and-microservices.html"}}]},{"type":"paragraph","text":"Tip: Continuously engage with devs to help them navigate the learning path. An example is by targeting email campaigns to people based on which stage they are in the learning path.","spans":[]},{"type":"paragraph","text":"In my next post, I will share what happens during and after the webinar. If you have any questions about the tips I’ve provided, feel free to leave a comment below!","spans":[]},{"type":"paragraph","text":"Karan MV is the India Outreach Manager at DigitalOcean. He is focused on building and helping developer and startup communities leverage DigitalOcean to solve their problems. He launched the webinars program at DigitalOcean and is currently involved in scaling it.","spans":[{"start":0,"end":264,"type":"em"}]}],"blog_post_date":"2018-07-09","tags":[{"tag1":{"tag":"Community","_linkType":"Link.document","_meta":{"uid":"community"}}}],"_meta":{"uid":"engaging-developer-communities-through-webinars-part-1"}}},{"node":{"author":{"_linkType":"Link.document","author_name":"Daniel Zaltsman","author_image":{"dimensions":{"width":188,"height":188},"alt":"Daniel Zaltsman","copyright":null,"url":"https://images.prismic.io/www-static/663d428f56c46eeb165c811add8f1f60402aa451_daniel_zaltsman-c47f4847.png?auto=compress,format"},"_meta":{"uid":"daniel_zaltsman"}},"blog_header_image":{"dimensions":{"width":784,"height":418},"alt":"High five illustration","copyright":null,"url":"https://images.prismic.io/www-static/3a111128190b5abcad25ab884b857ee71e4715e7_developer_advocates_blog_color4--1-.png?auto=compress,format"},"blog_headline":[{"type":"heading1","text":"May Community Doers: Open Source Contributors","spans":[]}],"blog_post_content":[{"type":"paragraph","text":"Since DigitalOcean came to be, the founders believed that the developer community is far greater than the sum of its parts. Six years later we continue to learn and grow thanks to the tireless work of our global community. Instrumental to increasing collaboration and ease-of-use, the Projects section of the Community received its first submission four years ago and today boasts a total of 186 apps, wrappers, and integrations using the DigitalOcean API.","spans":[{"start":285,"end":301,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/projects"}}]},{"type":"paragraph","text":"In this month’s “Doers” spotlight, we highlight three builders who continue to maintain technology that makes a difference for users in the DigitalOcean ecosystem. When they are not working on software engineering and DevOps, they give back in a way that enriches the community. Please join us in recognizing May’s featured Doers:","spans":[]},{"type":"paragraph","text":"Jeevanandam M. (@myjeevablog)","spans":[{"start":0,"end":14,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/users/jeevatkm"}},{"start":16,"end":28,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/myjeevablog"}}]},{"type":"paragraph","text":"When he is not building out and supporting aah, the secure, flexible, and rapid Go web framework, Jeeva has been making valuable contributions that enable developers to use DigitalOcean. Since early 2014, he has maintained a widely used DigitalOcean API client library written in Java. The client is used by the Jenkins DigitalOcean plugin, powering a large quantity of CI use cases on top of DigitalOcean. We are immensely thankful for Jeeva’s commitment to quality and community and believe this recognition is long due.","spans":[{"start":43,"end":46,"type":"hyperlink","data":{"link_type":"Web","url":"https://aahframework.org"}},{"start":237,"end":268,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/jeevatkm/digitalocean-api-java"}},{"start":312,"end":339,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/jenkinsci/digitalocean-plugin"}}]},{"type":"paragraph","text":"Lorenzo Setale (@koalalorenzo)","spans":[{"start":0,"end":14,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/users/koalalorenzo"}},{"start":16,"end":29,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/koalalorenzo"}}]},{"type":"paragraph","text":"Lorenzo is a Copenhagen-based Italian developer of ideas who has been involved in the community since 2012. Anyone who has spun up Droplets using the python-digitalocean Python library will be familiar with tireless Lorenzo’s work. He has long authored and maintained one of the most used and best supported DigitalOcean API libraries. A playground for experimentation for some is a tool to build someone’s first project, thanks to Lorenzo for the technology that keeps on giving.","spans":[{"start":150,"end":184,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/koalalorenzo/python-digitalocean"}},{"start":353,"end":368,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/gsechter/status/603644573999636481"}},{"start":397,"end":420,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/HunterJoz/status/939710970221551616"}}]},{"type":"paragraph","text":"Peter Souter (@petersouter)","spans":[{"start":0,"end":12,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/users/petems"}},{"start":14,"end":26,"type":"hyperlink","data":{"link_type":"Web","url":"https://twitter.com/PeterSouter"}}]},{"type":"paragraph","text":"Peter is an open source citizen that leads by example, noting on his blog that “as long as people are interested I will keep maintaining and helping with open source software I maintain.” with regards to his work on Tugboat, a CLI that predates doctl. Previously at Puppet, Peter currently works at HashiCorp out of London and we’re proud to say he's been around our community for a long time. In addition to being the main contributor to tugboat, he's had a few contributions to droplet_kit, the Ruby API client. Thanks for all your work, we appreciate it all.","spans":[{"start":55,"end":73,"type":"hyperlink","data":{"link_type":"Web","url":"https://petersouter.xyz/some-thoughts-on-maintaining-oss-that-has-an-official-competitor/"}},{"start":204,"end":223,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/petems/tugboat"}},{"start":245,"end":250,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/digitalocean/doctl"}},{"start":480,"end":491,"type":"hyperlink","data":{"link_type":"Web","url":"https://github.com/digitalocean/droplet_kit"}}]},{"type":"paragraph","text":"Jeeva, Lorenzo, and Peter showcase the qualities we are proud to see in our community and we hope that they inspire others as well.  We’re grateful to have this opportunity to recognize our amazing community contributors and if you’re interested in getting more involved in the DigitalOcean community, here are a few places to start:","spans":[]},{"type":"list-item","text":"Share a project that you’ve built with our API.","spans":[{"start":0,"end":15,"type":"hyperlink","data":{"link_type":"Web","url":"http://www.digitalocean.com/community/projects"}}]},{"type":"list-item","text":"Share your knowledge in our community Q&A section.","spans":[{"start":28,"end":49,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/questions"}}]},{"type":"list-item","text":"Join our Write for DOnations program and contribute to our library of tutorials.","spans":[{"start":9,"end":36,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.digitalocean.com/community/write-for-digitalocean"}}]},{"type":"list-item","text":"Get involved in your local DigitalOcean Meetup or start your own.","spans":[{"start":21,"end":46,"type":"hyperlink","data":{"link_type":"Web","url":"https://www.meetup.com/pro/digitalocean/"}},{"start":50,"end":64,"type":"hyperlink","data":{"link_type":"Web","url":"https://do.co/cometomycity"}}]},{"type":"list-item","text":"Read up on other Doers in our March and April editions.","spans":[{"start":30,"end":35,"type":"hyperlink","data":{"link_type":"Web","url":"https://blog.digitalocean.com/introducing-community-doers-march-edition/"}},{"start":40,"end":45,"type":"hyperlink","data":{"link_type":"Web","url":"https://blog.digitalocean.com/april-community-do-ers-meetup-edition/"}}]},{"type":"paragraph","text":"Want to recognize someone in the community? Leave their name in the comments or reach out to Doers [at] DigitalOcean [.] com.","spans":[]}],"blog_post_date":"2018-05-21","tags":[{"tag1":{"tag":"Community","_linkType":"Link.document","_meta":{"uid":"community"}}}],"_meta":{"uid":"may-community-doers"}}}]}}}